react-native-inapp-inspector 1.1.24 → 1.1.25

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 (79) hide show
  1. package/dist/commonjs/components/AnalyticsEventCard.js +94 -88
  2. package/dist/commonjs/components/ConsoleLogCard.js +49 -25
  3. package/dist/commonjs/components/Inspector/AnalyticsFilterModal.d.ts +7 -0
  4. package/dist/commonjs/components/Inspector/AnalyticsFilterModal.js +1002 -0
  5. package/dist/commonjs/components/Inspector/AnalyticsTab.js +637 -10
  6. package/dist/commonjs/components/Inspector/BundleTab.d.ts +1 -1
  7. package/dist/commonjs/components/Inspector/BundleTab.js +826 -406
  8. package/dist/commonjs/components/Inspector/InspectorContext.js +6 -1
  9. package/dist/commonjs/components/Inspector/InspectorHeader.js +13 -5
  10. package/dist/commonjs/components/Inspector/LogDetail.js +223 -111
  11. package/dist/commonjs/components/Inspector/MainScreen.js +1 -1
  12. package/dist/commonjs/components/Inspector/NetworkDetail.js +131 -81
  13. package/dist/commonjs/components/Inspector/PerformanceTab.js +20 -10
  14. package/dist/commonjs/components/NetworkIcons.d.ts +14 -0
  15. package/dist/commonjs/components/NetworkIcons.js +93 -1
  16. package/dist/commonjs/components/SegmentedTabs.js +31 -22
  17. package/dist/commonjs/components/TreeNode.js +13 -12
  18. package/dist/commonjs/constants/version.d.ts +1 -1
  19. package/dist/commonjs/constants/version.js +1 -1
  20. package/dist/commonjs/customHooks/bundleAnalyzer.d.ts +5 -1
  21. package/dist/commonjs/customHooks/bundleAnalyzer.js +497 -126
  22. package/dist/commonjs/customHooks/consoleLogger.js +83 -45
  23. package/dist/commonjs/customHooks/networkLogger.js +10 -1
  24. package/dist/commonjs/customHooks/performanceTracker.d.ts +8 -0
  25. package/dist/commonjs/customHooks/performanceTracker.js +44 -3
  26. package/dist/commonjs/decorators/index.d.ts +49 -0
  27. package/dist/commonjs/decorators/index.js +142 -0
  28. package/dist/commonjs/helpers/gaAnalyticsRegistry.d.ts +18 -0
  29. package/dist/commonjs/helpers/gaAnalyticsRegistry.js +174 -0
  30. package/dist/commonjs/helpers/index.d.ts +7 -4
  31. package/dist/commonjs/helpers/index.js +102 -64
  32. package/dist/commonjs/i18n/locales/en.json +51 -4
  33. package/dist/commonjs/index.d.ts +4 -0
  34. package/dist/commonjs/index.js +216 -8
  35. package/dist/commonjs/styles/index.d.ts +4 -1
  36. package/dist/commonjs/styles/index.js +22 -19
  37. package/dist/commonjs/types/enums.d.ts +115 -9
  38. package/dist/commonjs/types/enums.js +103 -2
  39. package/dist/commonjs/types/interfaces.d.ts +19 -0
  40. package/dist/esm/components/AnalyticsEventCard.js +95 -89
  41. package/dist/esm/components/ConsoleLogCard.js +49 -25
  42. package/dist/esm/components/Inspector/AnalyticsFilterModal.d.ts +7 -0
  43. package/dist/esm/components/Inspector/AnalyticsFilterModal.js +964 -0
  44. package/dist/esm/components/Inspector/AnalyticsTab.js +639 -12
  45. package/dist/esm/components/Inspector/BundleTab.d.ts +1 -1
  46. package/dist/esm/components/Inspector/BundleTab.js +827 -407
  47. package/dist/esm/components/Inspector/InspectorContext.js +1 -0
  48. package/dist/esm/components/Inspector/InspectorHeader.js +14 -6
  49. package/dist/esm/components/Inspector/LogDetail.js +223 -111
  50. package/dist/esm/components/Inspector/MainScreen.js +2 -2
  51. package/dist/esm/components/Inspector/NetworkDetail.js +132 -49
  52. package/dist/esm/components/Inspector/PerformanceTab.js +20 -10
  53. package/dist/esm/components/NetworkIcons.d.ts +14 -0
  54. package/dist/esm/components/NetworkIcons.js +78 -0
  55. package/dist/esm/components/SegmentedTabs.js +31 -22
  56. package/dist/esm/components/TreeNode.js +13 -12
  57. package/dist/esm/constants/version.d.ts +1 -1
  58. package/dist/esm/constants/version.js +1 -1
  59. package/dist/esm/customHooks/bundleAnalyzer.d.ts +5 -1
  60. package/dist/esm/customHooks/bundleAnalyzer.js +493 -125
  61. package/dist/esm/customHooks/consoleLogger.js +83 -45
  62. package/dist/esm/customHooks/networkLogger.js +10 -1
  63. package/dist/esm/customHooks/performanceTracker.d.ts +8 -0
  64. package/dist/esm/customHooks/performanceTracker.js +38 -1
  65. package/dist/esm/decorators/index.d.ts +49 -0
  66. package/dist/esm/decorators/index.js +137 -0
  67. package/dist/esm/helpers/gaAnalyticsRegistry.d.ts +18 -0
  68. package/dist/esm/helpers/gaAnalyticsRegistry.js +169 -0
  69. package/dist/esm/helpers/index.d.ts +7 -4
  70. package/dist/esm/helpers/index.js +100 -63
  71. package/dist/esm/i18n/locales/en.json +51 -4
  72. package/dist/esm/index.d.ts +4 -0
  73. package/dist/esm/index.js +190 -8
  74. package/dist/esm/styles/index.d.ts +4 -1
  75. package/dist/esm/styles/index.js +22 -19
  76. package/dist/esm/types/enums.d.ts +115 -9
  77. package/dist/esm/types/enums.js +102 -3
  78. package/dist/esm/types/interfaces.d.ts +19 -0
  79. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getCachedBundleAnalysis = exports.analyzeHostAppBundle = exports.parseBundleSource = exports.getHostScriptURL = void 0;
3
+ exports.getCachedBundleAnalysis = exports.clearCachedBundleAnalysis = exports.analyzeHostAppBundle = exports.parseBundleSource = exports.trackRuntimeDefine = exports.trackRuntimeModuleExecution = exports.getHostScriptURL = void 0;
4
4
  const AppColors_1 = require("../styles/AppColors");
5
5
  // ─── Real Host App Bundle Analyzer ──────────────────────────────────────────
6
6
  //
@@ -36,62 +36,356 @@ const getSourceCodeModule = () => {
36
36
  };
37
37
  const getHostScriptURL = () => {
38
38
  // 1. NativeModules.SourceCode (bridged) or SourceCode TurboModule (bridgeless / new arch)
39
- const scriptURL = getSourceCodeModule()?.scriptURL;
40
- if (typeof scriptURL === 'string' && scriptURL.length > 0) {
41
- return scriptURL;
39
+ try {
40
+ const scriptURL = getSourceCodeModule()?.scriptURL;
41
+ if (typeof scriptURL === 'string' && scriptURL.length > 0) {
42
+ return scriptURL;
43
+ }
42
44
  }
43
- // 2. Android legacy: PlatformConstants.serverHost -> "localhost:8082"
45
+ catch { }
46
+ // 2. Android legacy & new arch: PlatformConstants.serverHost, DevSettings, or AndroidConstants
44
47
  try {
45
- const serverHost = react_native_1.NativeModules?.PlatformConstants?.serverHost;
48
+ const serverHost = react_native_1.NativeModules?.PlatformConstants?.serverHost ||
49
+ react_native_1.NativeModules?.AndroidConstants?.serverHost ||
50
+ react_native_1.NativeModules?.DevSettings?.serverHost ||
51
+ react_native_1.NativeModules?.DevSettings?.getConstants?.()?.serverHost;
46
52
  if (typeof serverHost === 'string' && serverHost.length > 0) {
47
- return `http://${serverHost}/index.bundle?platform=${react_native_1.Platform.OS}&dev=true`;
53
+ const formattedHost = serverHost.startsWith('http') ? serverHost : `http://${serverHost}`;
54
+ return `${formattedHost}/index.bundle?platform=${react_native_1.Platform.OS}&dev=true`;
55
+ }
56
+ }
57
+ catch { }
58
+ // 3. Fallback to global dev config if defined
59
+ try {
60
+ const globalHost = globalThis?.__DEV_SERVER_URL__ ||
61
+ globalThis?.__METRO_SERVER_HOST__;
62
+ if (typeof globalHost === 'string' && globalHost.length > 0) {
63
+ return globalHost.startsWith('http')
64
+ ? globalHost
65
+ : `http://${globalHost}/index.bundle?platform=${react_native_1.Platform.OS}&dev=true`;
48
66
  }
49
67
  }
50
68
  catch { }
51
69
  return '';
52
70
  };
53
71
  exports.getHostScriptURL = getHostScriptURL;
54
- const DEV_SERVER_PORTS = [8081, 8082, 8083, 8084, 8090, 8080, 19000];
72
+ // Expanded list of common Metro & dev server ports
73
+ const DEV_SERVER_PORTS = [8081, 8082, 8083, 8084, 8085, 8086, 8087, 8088, 8089, 8090, 8080, 19000, 19001, 3000];
55
74
  const buildProbeUrls = (scriptURL) => {
56
75
  const urls = [];
76
+ let extractedHost = null;
77
+ let extractedPort = null;
57
78
  if (typeof scriptURL === 'string' && scriptURL.startsWith('http')) {
58
79
  urls.push(scriptURL);
80
+ const urlMatch = scriptURL.match(/^https?:\/\/([^:/]+)(?::(\d+))?/);
81
+ if (urlMatch) {
82
+ extractedHost = urlMatch[1];
83
+ if (urlMatch[2]) {
84
+ extractedPort = parseInt(urlMatch[2], 10);
85
+ }
86
+ }
59
87
  }
60
- const portMatch = typeof scriptURL === 'string' ? scriptURL.match(/:(\d{2,5})/) : null;
61
- const ports = portMatch ? [parseInt(portMatch[1], 10)] : DEV_SERVER_PORTS;
88
+ // Prioritize dynamically extracted port first
89
+ const ports = extractedPort
90
+ ? Array.from(new Set([extractedPort, ...DEV_SERVER_PORTS]))
91
+ : DEV_SERVER_PORTS;
62
92
  const hosts = react_native_1.Platform.OS === 'android'
63
- ? ['localhost', '127.0.0.1', '10.0.2.2']
93
+ ? ['localhost', '127.0.0.1', '10.0.2.2', '10.0.3.2']
64
94
  : ['localhost', '127.0.0.1'];
65
- for (const host of hosts) {
66
- for (const port of ports) {
95
+ if (extractedHost && !hosts.includes(extractedHost)) {
96
+ hosts.unshift(extractedHost);
97
+ }
98
+ for (const port of ports) {
99
+ for (const host of hosts) {
67
100
  urls.push(`http://${host}:${port}/index.bundle?platform=${react_native_1.Platform.OS}&dev=true`);
68
101
  }
69
102
  }
70
103
  return Array.from(new Set(urls));
71
104
  };
72
- const fetchWithTimeout = async (url, timeoutMs) => {
105
+ const promiseAny = (promises) => {
106
+ return new Promise((resolve, reject) => {
107
+ let pendingCount = promises.length;
108
+ if (pendingCount === 0) {
109
+ reject(new Error('All promises rejected'));
110
+ return;
111
+ }
112
+ promises.forEach(p => {
113
+ Promise.resolve(p)
114
+ .then(resolve)
115
+ .catch(() => {
116
+ pendingCount--;
117
+ if (pendingCount === 0) {
118
+ reject(new Error('All promises rejected'));
119
+ }
120
+ });
121
+ });
122
+ });
123
+ };
124
+ const probeCandidateUrlsInParallel = async (urls, timeoutMs = 4000) => {
125
+ if (urls.length === 0)
126
+ return null;
127
+ const controller = new AbortController();
128
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
129
+ const fetchSingle = async (url) => {
130
+ const res = await fetch(url, { signal: controller.signal });
131
+ if (!res.ok) {
132
+ throw new Error(`HTTP ${res.status}`);
133
+ }
134
+ const contentLength = res.headers.get('content-length');
135
+ const text = await res.text();
136
+ if (!text || text.length === 0) {
137
+ throw new Error('Empty response');
138
+ }
139
+ const bytes = contentLength ? parseInt(contentLength, 10) || text.length : text.length;
140
+ return { text, bytes, url };
141
+ };
73
142
  try {
74
- const controller = new AbortController();
75
- const timer = setTimeout(() => controller.abort(), timeoutMs);
76
- try {
77
- const res = await fetch(url, { signal: controller.signal });
78
- if (!res.ok)
79
- return { ok: false };
80
- const contentLength = res.headers.get('content-length');
81
- const text = await res.text();
82
- return {
83
- ok: true,
84
- text,
85
- bytes: contentLength ? parseInt(contentLength, 10) || text.length : text.length,
143
+ // Probe candidate dev servers concurrently — whichever responds first wins
144
+ const result = await promiseAny(urls.map(url => fetchSingle(url)));
145
+ controller.abort();
146
+ return result;
147
+ }
148
+ catch {
149
+ return null;
150
+ }
151
+ finally {
152
+ clearTimeout(timer);
153
+ }
154
+ };
155
+ const MAX_ARG_SCAN_LEN = 200000;
156
+ const scanCallArguments = (text, openParenIndex) => {
157
+ let depth = 0;
158
+ let bracketDepth = 0;
159
+ let quote = null;
160
+ const commas = [];
161
+ const end = Math.min(text.length, openParenIndex + MAX_ARG_SCAN_LEN);
162
+ for (let i = openParenIndex; i < end; i++) {
163
+ const ch = text[i];
164
+ if (quote) {
165
+ if (ch === '\\') {
166
+ i++;
167
+ continue;
168
+ }
169
+ if (ch === quote)
170
+ quote = null;
171
+ continue;
172
+ }
173
+ if (ch === '"' || ch === "'" || ch === '`') {
174
+ quote = ch;
175
+ continue;
176
+ }
177
+ if (ch === '(') {
178
+ depth++;
179
+ continue;
180
+ }
181
+ if (ch === ')') {
182
+ if (depth === 0)
183
+ return null;
184
+ depth--;
185
+ if (depth === 0) {
186
+ if (commas.length === 0)
187
+ return null;
188
+ const args = [];
189
+ let prev = openParenIndex + 1;
190
+ for (const c of commas) {
191
+ args.push(text.slice(prev, c));
192
+ prev = c + 1;
193
+ }
194
+ args.push(text.slice(prev, i));
195
+ return args;
196
+ }
197
+ continue;
198
+ }
199
+ if (ch === '[') {
200
+ bracketDepth++;
201
+ continue;
202
+ }
203
+ if (ch === ']') {
204
+ if (bracketDepth > 0)
205
+ bracketDepth--;
206
+ continue;
207
+ }
208
+ if (ch === ',' && depth === 1 && bracketDepth === 0)
209
+ commas.push(i);
210
+ }
211
+ return null;
212
+ };
213
+ // ─── Runtime Module & Define Tracking ────────────────────────────────────────
214
+ // Captures modules registered via Metro's global __d and executed via __r.
215
+ const executedModuleIds = new Set();
216
+ const runtimeModules = new Map();
217
+ const trackRuntimeModuleExecution = () => {
218
+ try {
219
+ const g = globalThis;
220
+ const r = g.__r;
221
+ if (typeof r === 'function' && !r.__inspectorTracked) {
222
+ const tracked = function (moduleId, ...rest) {
223
+ executedModuleIds.add(Number(moduleId));
224
+ return r.call(this, moduleId, ...rest);
86
225
  };
226
+ Object.defineProperty(tracked, '__inspectorTracked', { value: true, enumerable: false });
227
+ g.__r = tracked;
87
228
  }
88
- finally {
89
- clearTimeout(timer);
229
+ }
230
+ catch { }
231
+ };
232
+ exports.trackRuntimeModuleExecution = trackRuntimeModuleExecution;
233
+ const trackRuntimeDefine = () => {
234
+ try {
235
+ const g = globalThis;
236
+ const originalD = g.__d;
237
+ if (typeof originalD === 'function' && !originalD.__inspectorTracked) {
238
+ const trackedD = function (factory, moduleId, dependencyMap, verboseName, ...rest) {
239
+ if (typeof verboseName === 'string' && verboseName.length > 0) {
240
+ runtimeModules.set(Number(moduleId), { id: Number(moduleId), path: verboseName });
241
+ }
242
+ return originalD.call(this, factory, moduleId, dependencyMap, verboseName, ...rest);
243
+ };
244
+ Object.defineProperty(trackedD, '__inspectorTracked', { value: true, enumerable: false });
245
+ g.__d = trackedD;
90
246
  }
91
247
  }
92
- catch {
93
- return { ok: false };
248
+ catch { }
249
+ };
250
+ exports.trackRuntimeDefine = trackRuntimeDefine;
251
+ (0, exports.trackRuntimeModuleExecution)();
252
+ (0, exports.trackRuntimeDefine)();
253
+ const extractBundleModules = (bundleText) => {
254
+ const modules = [];
255
+ const seenIds = new Set();
256
+ if (bundleText && bundleText.length > 0) {
257
+ // Primary high-speed Metro tail regex: matches }, <id>, [<deps>], "<path>"
258
+ const tailRegex = /\}\s*,\s*(\d+)\s*,\s*\[([\d,\s]*)\]\s*,\s*(?:"([^"\r\n]+)"|'([^'\r\n]+)')/g;
259
+ let match;
260
+ while ((match = tailRegex.exec(bundleText)) !== null) {
261
+ const id = parseInt(match[1], 10);
262
+ const depsStr = match[2];
263
+ const path = match[3] || match[4] || '';
264
+ const deps = depsStr
265
+ ? depsStr
266
+ .split(',')
267
+ .map(s => parseInt(s.trim(), 10))
268
+ .filter(n => !isNaN(n))
269
+ : [];
270
+ if (!seenIds.has(id) && path.length > 0) {
271
+ seenIds.add(id);
272
+ modules.push({ id, deps, path });
273
+ }
274
+ }
275
+ // Secondary fallback if tailRegex didn't find modules
276
+ if (modules.length === 0) {
277
+ const defineRe = /__d\s*\(/g;
278
+ let m;
279
+ while ((m = defineRe.exec(bundleText)) !== null) {
280
+ const openParen = m.index + m[0].length - 1;
281
+ const args = scanCallArguments(bundleText, openParen);
282
+ if (!args || args.length < 4)
283
+ continue;
284
+ const idMatch = args[1].trim().match(/^(\d+)$/);
285
+ if (!idMatch)
286
+ continue;
287
+ const idNum = parseInt(idMatch[1], 10);
288
+ if (seenIds.has(idNum))
289
+ continue;
290
+ const depsMatch = args[2].match(/\[([^\]]*)\]/);
291
+ const deps = [];
292
+ if (depsMatch) {
293
+ const depRe = /\d+/g;
294
+ let d;
295
+ while ((d = depRe.exec(depsMatch[1])) !== null) {
296
+ deps.push(parseInt(d[0], 10));
297
+ }
298
+ }
299
+ const pathRaw = args[3].trim();
300
+ const pathMatch = pathRaw.match(/^["']([^"']+)["']$/);
301
+ const path = pathMatch ? pathMatch[1] : pathRaw;
302
+ if (path.length === 0 || path.length > 500)
303
+ continue;
304
+ seenIds.add(idNum);
305
+ modules.push({ id: idNum, deps, path });
306
+ }
307
+ }
308
+ }
309
+ // Merge any runtime modules captured via global.__d
310
+ runtimeModules.forEach((item, id) => {
311
+ if (!seenIds.has(id)) {
312
+ seenIds.add(id);
313
+ modules.push({ id, deps: [], path: item.path });
314
+ }
315
+ });
316
+ return modules;
317
+ };
318
+ const getStaticStartupModuleIds = (bundleText) => {
319
+ const ids = new Set();
320
+ const startupRe = /__r\s*\(\s*(\d+)\s*\)/g;
321
+ let m;
322
+ while ((m = startupRe.exec(bundleText)) !== null) {
323
+ ids.add(parseInt(m[1], 10));
324
+ }
325
+ return ids;
326
+ };
327
+ const findCommonDirectoryPrefix = (paths) => {
328
+ const absPaths = paths.filter(p => p.startsWith('/'));
329
+ if (absPaths.length === 0)
330
+ return '';
331
+ if (absPaths.length === 1) {
332
+ const lastSlash = absPaths[0].lastIndexOf('/');
333
+ return lastSlash > 0 ? absPaths[0].slice(0, lastSlash + 1) : '';
334
+ }
335
+ const splitPaths = absPaths.map(p => p.split('/'));
336
+ let commonIndex = 0;
337
+ const first = splitPaths[0];
338
+ while (commonIndex < first.length - 1) {
339
+ const segment = first[commonIndex];
340
+ if (splitPaths.every(p => p[commonIndex] === segment)) {
341
+ commonIndex++;
342
+ }
343
+ else {
344
+ break;
345
+ }
346
+ }
347
+ if (commonIndex === 0)
348
+ return '';
349
+ return first.slice(0, commonIndex).join('/') + '/';
350
+ };
351
+ const isExternalPackage = (rawPath) => {
352
+ // 1. Standard node_modules
353
+ if (rawPath.includes('node_modules/')) {
354
+ const pkgMatch = rawPath.match(/node_modules\/(?:@([^/]+)\/([^/]+)|([^/]+))/);
355
+ if (pkgMatch) {
356
+ const pkgName = pkgMatch[1] && pkgMatch[2] ? `@${pkgMatch[1]}/${pkgMatch[2]}` : pkgMatch[3];
357
+ return { isPkg: true, pkgName };
358
+ }
359
+ return { isPkg: true };
360
+ }
361
+ // 2. React Native In-App Inspector (when linked or embedded)
362
+ if (rawPath.includes('react-native-inapp-inspector')) {
363
+ return { isPkg: true, pkgName: 'react-native-inapp-inspector' };
364
+ }
365
+ // 3. Yarn berry / pnpm virtual stores
366
+ if (rawPath.includes('.yarn/') || rawPath.includes('.pnpm/')) {
367
+ return { isPkg: true };
368
+ }
369
+ // 4. Compiled package dist / build folders outside project source
370
+ if (rawPath.includes('/dist/esm/') ||
371
+ rawPath.includes('/dist/commonjs/') ||
372
+ rawPath.includes('/dist/cjs/') ||
373
+ rawPath.startsWith('dist/esm/') ||
374
+ rawPath.startsWith('dist/commonjs/') ||
375
+ rawPath.startsWith('dist/') ||
376
+ rawPath.includes('/react-native-inapp-inspector/dist/')) {
377
+ return { isPkg: true, pkgName: 'react-native-inapp-inspector' };
94
378
  }
379
+ return { isPkg: false };
380
+ };
381
+ const isInternalModule = (rawPath) => {
382
+ return (rawPath.startsWith('<<') ||
383
+ rawPath.startsWith('[metro]') ||
384
+ rawPath.includes('metro-runtime') ||
385
+ rawPath.includes('polyfills/require.js') ||
386
+ rawPath.includes('prelude_commonjs') ||
387
+ rawPath.includes('Libraries/Core/InitializeCore.js') ||
388
+ rawPath.includes('@babel/runtime'));
95
389
  };
96
390
  /**
97
391
  * Parses raw Metro bundle source code to discover real modules and packages in the host app.
@@ -100,79 +394,156 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
100
394
  const isHermes = Boolean(globalThis.HermesInternal);
101
395
  const totalDevMb = Number((totalBytes / (1024 * 1024)).toFixed(2));
102
396
  const totalDevKb = Math.round(totalBytes / 1024);
397
+ const modules = extractBundleModules(bundleText);
398
+ const startupIds = getStaticStartupModuleIds(bundleText);
399
+ // Build module map for dependency graph traversal
400
+ const modMap = new Map();
401
+ for (const mod of modules) {
402
+ modMap.set(mod.id, mod);
403
+ }
404
+ // Transitive Reachability: traverse the dependency graph starting from
405
+ // entrypoints (index.js, App.tsx, app.json, startupIds, executedModuleIds)
406
+ const consumedIds = new Set();
407
+ const queue = [];
408
+ const markConsumed = (id) => {
409
+ if (!consumedIds.has(id)) {
410
+ consumedIds.add(id);
411
+ queue.push(id);
412
+ }
413
+ };
414
+ // Seed with root entrypoints and runtime executions
415
+ markConsumed(0);
416
+ startupIds.forEach(id => markConsumed(id));
417
+ executedModuleIds.forEach(id => markConsumed(id));
418
+ for (const mod of modules) {
419
+ const lower = mod.path.toLowerCase().replace(/\\/g, '/');
420
+ if (lower.endsWith('/index.js') ||
421
+ lower.endsWith('/index.ts') ||
422
+ lower.endsWith('/index.tsx') ||
423
+ lower === 'index.js' ||
424
+ lower.endsWith('/app.tsx') ||
425
+ lower.endsWith('/app.js') ||
426
+ lower.endsWith('/app.jsx') ||
427
+ lower === 'app.tsx' ||
428
+ lower === 'app.js' ||
429
+ lower.endsWith('app.json')) {
430
+ markConsumed(mod.id);
431
+ }
432
+ }
433
+ // Transitive BFS dependency traversal
434
+ while (queue.length > 0) {
435
+ const currId = queue.shift();
436
+ const currMod = modMap.get(currId);
437
+ if (currMod && currMod.deps) {
438
+ for (const depId of currMod.deps) {
439
+ markConsumed(depId);
440
+ }
441
+ }
442
+ }
103
443
  const discoveredPackagesMap = new Map();
444
+ const rawProjectItems = [];
445
+ for (const mod of modules) {
446
+ const rawPath = mod.path.replace(/\\/g, '/'); // Normalize Windows backslashes
447
+ const { isPkg, pkgName } = isExternalPackage(rawPath);
448
+ if (isPkg) {
449
+ if (pkgName && !pkgName.startsWith('.')) {
450
+ discoveredPackagesMap.set(pkgName, (discoveredPackagesMap.get(pkgName) || 0) + 1);
451
+ }
452
+ continue;
453
+ }
454
+ // Filter out internal Metro polyfills / virtual modules
455
+ if (isInternalModule(rawPath)) {
456
+ continue;
457
+ }
458
+ rawProjectItems.push({ mod, rawPath });
459
+ }
460
+ // Calculate common prefix for absolute paths to cleanly extract host app project files
461
+ const commonPrefix = findCommonDirectoryPrefix(rawProjectItems.map(item => item.rawPath));
104
462
  const discoveredFiles = [];
105
- // Match module declarations: __d(function(...), id, [...], "path/to/module.js") or comments
106
- // In Metro: __d(function(...), 42, [1, 2], "node_modules/lodash/index.js")
107
- const modulePathRegex = /(?:__d\s*\([^,]+,[^,]+,[^,]+,["']([^"']+)["']|\/\/\s*@metro-module-path\s+([^\n\r]+)|["']((?:node_modules|\.|\/|[a-zA-Z0-9_-]+)\/[^"']+\.[a-zA-Z0-9]+)["'])/g;
108
- let match;
109
463
  let fileIdx = 0;
110
- const seenPaths = new Set();
111
- // Extract from text
112
- while ((match = modulePathRegex.exec(bundleText)) !== null) {
113
- const rawPath = match[1] || match[2] || match[3];
114
- if (!rawPath || seenPaths.has(rawPath) || rawPath.length > 200)
464
+ for (const { mod, rawPath } of rawProjectItems) {
465
+ let cleanPath = rawPath;
466
+ if (commonPrefix && cleanPath.startsWith(commonPrefix)) {
467
+ cleanPath = cleanPath.slice(commonPrefix.length);
468
+ }
469
+ // Clean leading ../ sequences, ./, and leading /
470
+ cleanPath = cleanPath.replace(/^(\.\.\/)+/, '').replace(/^\.\//, '').replace(/^\/+/, '');
471
+ if (!cleanPath)
472
+ cleanPath = rawPath.split('/').pop() || rawPath;
473
+ const isLocalFile = cleanPath.endsWith('.tsx') ||
474
+ cleanPath.endsWith('.ts') ||
475
+ cleanPath.endsWith('.jsx') ||
476
+ cleanPath.endsWith('.js') ||
477
+ cleanPath.endsWith('.mjs') ||
478
+ cleanPath.endsWith('.cjs') ||
479
+ cleanPath.endsWith('.json') ||
480
+ cleanPath.endsWith('.png') ||
481
+ cleanPath.endsWith('.jpg') ||
482
+ cleanPath.endsWith('.jpeg') ||
483
+ cleanPath.endsWith('.gif') ||
484
+ cleanPath.endsWith('.webp') ||
485
+ cleanPath.endsWith('.svg') ||
486
+ cleanPath.endsWith('.bmp') ||
487
+ cleanPath.endsWith('.ico') ||
488
+ cleanPath.endsWith('.ttf') ||
489
+ cleanPath.endsWith('.otf') ||
490
+ cleanPath.endsWith('.woff') ||
491
+ cleanPath.endsWith('.woff2') ||
492
+ cleanPath.endsWith('.css') ||
493
+ cleanPath.endsWith('.scss') ||
494
+ cleanPath.endsWith('.html') ||
495
+ cleanPath.endsWith('.md') ||
496
+ cleanPath.endsWith('.graphql') ||
497
+ cleanPath.endsWith('.gql');
498
+ if (!isLocalFile)
115
499
  continue;
116
- seenPaths.add(rawPath);
117
- const isNodeModule = rawPath.includes('node_modules/');
118
- if (isNodeModule) {
119
- const pkgMatch = rawPath.match(/node_modules\/(?:@([^/]+)\/([^/]+)|([^/]+))/);
120
- if (pkgMatch) {
121
- const pkgName = pkgMatch[1] && pkgMatch[2] ? `@${pkgMatch[1]}/${pkgMatch[2]}` : pkgMatch[3];
122
- if (pkgName && !pkgName.startsWith('.')) {
123
- discoveredPackagesMap.set(pkgName, (discoveredPackagesMap.get(pkgName) || 0) + 1);
124
- }
125
- }
500
+ const ext = cleanPath.split('.').pop()?.toUpperCase() || 'JS';
501
+ const name = cleanPath.split('/').pop() || cleanPath;
502
+ let category = 'javascript';
503
+ let color = AppColors_1.AppColors.indigo500;
504
+ if (ext === 'TSX' || ext === 'TS') {
505
+ category = 'typescript';
506
+ color = AppColors_1.AppColors.sky500;
126
507
  }
127
- else if (rawPath.endsWith('.tsx') ||
128
- rawPath.endsWith('.ts') ||
129
- rawPath.endsWith('.jsx') ||
130
- rawPath.endsWith('.js') ||
131
- rawPath.endsWith('.json') ||
132
- rawPath.endsWith('.png') ||
133
- rawPath.endsWith('.jpg') ||
134
- rawPath.endsWith('.svg') ||
135
- rawPath.endsWith('.ttf')) {
136
- const ext = rawPath.split('.').pop()?.toUpperCase() || 'JS';
137
- const name = rawPath.split('/').pop() || rawPath;
138
- let category = 'javascript';
139
- let color = AppColors_1.AppColors.indigo500;
140
- if (ext === 'TSX' || ext === 'TS') {
141
- category = 'typescript';
142
- color = AppColors_1.AppColors.sky500;
143
- }
144
- else if (ext === 'PNG' || ext === 'JPG' || ext === 'SVG') {
145
- category = 'image';
146
- color = AppColors_1.AppColors.pink500;
147
- }
148
- else if (ext === 'TTF' || ext === 'OTF') {
149
- category = 'font';
150
- color = AppColors_1.AppColors.purple500;
151
- }
152
- else if (ext === 'JSON') {
153
- category = 'json';
154
- color = AppColors_1.AppColors.emerald500;
155
- }
156
- // Approximate module size from total and count
157
- const approxKb = Math.max(2, Math.round((totalDevKb * 0.15) / Math.max(seenPaths.size, 20)));
158
- discoveredFiles.push({
159
- id: `host-file-${fileIdx++}`,
160
- name,
161
- path: rawPath,
162
- ext,
163
- category,
164
- sizeKb: approxKb,
165
- meta: `Active Host App Module • ${category.toUpperCase()}`,
166
- color,
167
- status: 'optimal',
168
- advice: 'Bundled into Host App Development Runtime',
169
- isConsumed: true,
170
- });
508
+ else if (ext === 'PNG' ||
509
+ ext === 'JPG' ||
510
+ ext === 'JPEG' ||
511
+ ext === 'GIF' ||
512
+ ext === 'WEBP' ||
513
+ ext === 'SVG' ||
514
+ ext === 'BMP' ||
515
+ ext === 'ICO') {
516
+ category = 'image';
517
+ color = AppColors_1.AppColors.pink500;
518
+ }
519
+ else if (ext === 'TTF' || ext === 'OTF' || ext === 'WOFF' || ext === 'WOFF2') {
520
+ category = 'font';
521
+ color = AppColors_1.AppColors.purple500;
522
+ }
523
+ else if (ext === 'JSON' || ext === 'CSS' || ext === 'SCSS' || ext === 'HTML' || ext === 'MD' || ext === 'GRAPHQL' || ext === 'GQL') {
524
+ category = 'json';
525
+ color = AppColors_1.AppColors.emerald500;
171
526
  }
172
- if (seenPaths.size >= 1200)
173
- break; // Limit parsing overhead
527
+ // Approximate module size from total and module count
528
+ const approxKb = Math.max(2, Math.round((totalDevKb * 0.15) / Math.max(modules.length, 20)));
529
+ const isConsumed = consumedIds.has(mod.id);
530
+ discoveredFiles.push({
531
+ id: `host-file-${fileIdx++}`,
532
+ name,
533
+ path: cleanPath,
534
+ ext,
535
+ category,
536
+ sizeKb: approxKb,
537
+ meta: `Active Host App Module • ${category.toUpperCase()}`,
538
+ color,
539
+ status: isConsumed ? 'optimal' : 'warning',
540
+ advice: isConsumed
541
+ ? 'In-Use: Bundled and active in host application dependency tree'
542
+ : 'Not consumed: Defined in the bundle but not referenced in active execution tree',
543
+ isConsumed,
544
+ });
174
545
  }
175
- // If no files matched via regex (e.g. minified or obfuscated bundle), synthesize from loaded modules
546
+ // If no modules could be parsed (e.g. Hermes bytecode / obfuscated bundle), synthesize from loaded modules
176
547
  if (discoveredFiles.length === 0) {
177
548
  discoveredFiles.push({
178
549
  id: 'hf-1',
@@ -361,7 +732,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
361
732
  const iosComponents = [
362
733
  {
363
734
  id: 'ios-c1',
364
- name: 'Dynamic Frameworks & Pods',
735
+ name: 'Frameworks & Dynamic Pods',
365
736
  category: 'frameworks',
366
737
  sizeMb: nativeFrameworksMb,
367
738
  pct: Number(((nativeFrameworksMb / iosInstallMb) * 100).toFixed(1)),
@@ -371,7 +742,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
371
742
  },
372
743
  {
373
744
  id: 'ios-c2',
374
- name: 'Native Mach-O Executable (ARM64)',
745
+ name: 'Mach-O Executable (ARM64)',
375
746
  category: 'native',
376
747
  sizeMb: nativeMachoMb,
377
748
  pct: Number(((nativeMachoMb / iosInstallMb) * 100).toFixed(1)),
@@ -381,7 +752,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
381
752
  },
382
753
  {
383
754
  id: 'ios-c3',
384
- name: 'Asset Catalog (Assets.car & Media)',
755
+ name: 'Asset Catalog (Assets.car)',
385
756
  category: 'assets',
386
757
  sizeMb: assetsCatalogMb,
387
758
  pct: Number(((assetsCatalogMb / iosInstallMb) * 100).toFixed(1)),
@@ -391,7 +762,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
391
762
  },
392
763
  {
393
764
  id: 'ios-c4',
394
- name: 'Hermes Bytecode (main.jsbundle / .hbc)',
765
+ name: 'Hermes Bytecode (main.jsbundle)',
395
766
  category: 'js',
396
767
  sizeMb: releaseJsMb,
397
768
  pct: Number(((releaseJsMb / iosInstallMb) * 100).toFixed(1)),
@@ -401,7 +772,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
401
772
  },
402
773
  {
403
774
  id: 'ios-c5',
404
- name: 'App Metadata & Code Signatures',
775
+ name: 'Metadata & Code Signatures',
405
776
  category: 'meta',
406
777
  sizeMb: metadataMb,
407
778
  pct: Number(((metadataMb / iosInstallMb) * 100).toFixed(1)),
@@ -413,7 +784,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
413
784
  const androidComponents = [
414
785
  {
415
786
  id: 'and-c1',
416
- name: 'Native C++ Shared Libraries (lib/arm64-v8a/)',
787
+ name: 'Native C++ Libraries (.so)',
417
788
  category: 'native',
418
789
  sizeMb: androidCppMb,
419
790
  pct: Number(((androidCppMb / androidInstallMb) * 100).toFixed(1)),
@@ -423,7 +794,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
423
794
  },
424
795
  {
425
796
  id: 'and-c2',
426
- name: 'Compiled DEX Bytecode (classes.dex)',
797
+ name: 'Compiled DEX (classes.dex)',
427
798
  category: 'frameworks',
428
799
  sizeMb: androidDexMb,
429
800
  pct: Number(((androidDexMb / androidInstallMb) * 100).toFixed(1)),
@@ -433,7 +804,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
433
804
  },
434
805
  {
435
806
  id: 'and-c3',
436
- name: 'Android Resources & Drawables (res/)',
807
+ name: 'Android Resources (res/)',
437
808
  category: 'assets',
438
809
  sizeMb: androidResMb,
439
810
  pct: Number(((androidResMb / androidInstallMb) * 100).toFixed(1)),
@@ -443,7 +814,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
443
814
  },
444
815
  {
445
816
  id: 'and-c4',
446
- name: 'Hermes Bytecode (index.android.bundle)',
817
+ name: 'Hermes Bytecode (Android)',
447
818
  category: 'js',
448
819
  sizeMb: releaseJsMb,
449
820
  pct: Number(((releaseJsMb / androidInstallMb) * 100).toFixed(1)),
@@ -453,7 +824,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
453
824
  },
454
825
  {
455
826
  id: 'and-c5',
456
- name: 'Android Manifest & Signatures (META-INF/)',
827
+ name: 'Manifest & Signatures (META-INF)',
457
828
  category: 'meta',
458
829
  sizeMb: 2.6,
459
830
  pct: Number(((2.6 / androidInstallMb) * 100).toFixed(1)),
@@ -469,7 +840,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
469
840
  const androidApkComponents = [
470
841
  {
471
842
  id: 'apk-c1',
472
- name: 'Multi-ABI Native C++ Libraries (arm64, v7a, x86_64)',
843
+ name: 'Multi-ABI C++ Libraries (.so)',
473
844
  category: 'native',
474
845
  sizeMb: androidMultiAbiCppMb,
475
846
  pct: Number(((androidMultiAbiCppMb / androidApkInstallMb) * 100).toFixed(1)),
@@ -489,7 +860,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
489
860
  },
490
861
  {
491
862
  id: 'apk-c3',
492
- name: 'Android Resources & Assets (res/, assets/)',
863
+ name: 'Android Resources & Assets (res/)',
493
864
  category: 'assets',
494
865
  sizeMb: androidResMb,
495
866
  pct: Number(((androidResMb / androidApkInstallMb) * 100).toFixed(1)),
@@ -499,7 +870,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
499
870
  },
500
871
  {
501
872
  id: 'apk-c4',
502
- name: 'Hermes Bytecode Bundle (index.android.bundle)',
873
+ name: 'Hermes Bytecode Bundle (Android)',
503
874
  category: 'js',
504
875
  sizeMb: releaseJsMb,
505
876
  pct: Number(((releaseJsMb / androidApkInstallMb) * 100).toFixed(1)),
@@ -509,7 +880,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
509
880
  },
510
881
  {
511
882
  id: 'apk-c5',
512
- name: 'Android Manifest & v1/v2/v3 Signatures (META-INF/)',
883
+ name: 'Manifest & Signatures (META-INF)',
513
884
  category: 'meta',
514
885
  sizeMb: 3.2,
515
886
  pct: Number(((3.2 / androidApkInstallMb) * 100).toFixed(1)),
@@ -525,7 +896,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
525
896
  totalDevMb,
526
897
  totalDevKb,
527
898
  isHermes,
528
- moduleCount: seenPaths.size || discoveredFiles.length + packagesList.length,
899
+ moduleCount: modules.length || discoveredFiles.length + packagesList.length,
529
900
  packageCount: packagesList.length,
530
901
  filesCount: discoveredFiles.length,
531
902
  splitUp: {
@@ -583,9 +954,13 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) =>
583
954
  exports.parseBundleSource = parseBundleSource;
584
955
  /**
585
956
  * Automatically fetch and analyze the real running bundle for the host app.
957
+ * Supports dynamic port detection (8081, 8082, 8083, etc.) and parallel dev server probing.
586
958
  */
587
- const analyzeHostAppBundle = async () => {
588
- if (cachedAnalysis)
959
+ const analyzeHostAppBundle = async (forceRefresh = false) => {
960
+ if (forceRefresh) {
961
+ cachedAnalysis = null;
962
+ }
963
+ if (cachedAnalysis && !forceRefresh)
589
964
  return cachedAnalysis;
590
965
  if (isAnalyzing) {
591
966
  return new Promise(resolve => {
@@ -594,19 +969,11 @@ const analyzeHostAppBundle = async () => {
594
969
  }
595
970
  isAnalyzing = true;
596
971
  const scriptURL = (0, exports.getHostScriptURL)();
597
- // Probe candidate dev-server URLs until one serves the JS bundle
598
- let fetched = null;
599
- let fetchedUrl = '';
600
- for (const url of buildProbeUrls(scriptURL)) {
601
- const probe = await fetchWithTimeout(url, 3000);
602
- if (probe.ok && probe.text && probe.text.length > 0) {
603
- fetched = { text: probe.text, bytes: probe.bytes ?? probe.text.length };
604
- fetchedUrl = url;
605
- break;
606
- }
607
- }
608
- if (fetched) {
609
- const result = (0, exports.parseBundleSource)(fetched.text, fetched.bytes, fetchedUrl);
972
+ const probeUrls = buildProbeUrls(scriptURL);
973
+ // Probe all candidate dev-server ports and hosts in parallel for instant response
974
+ const fetched = await probeCandidateUrlsInParallel(probeUrls, 3500);
975
+ if (fetched && fetched.text && fetched.text.length > 0) {
976
+ const result = (0, exports.parseBundleSource)(fetched.text, fetched.bytes, fetched.url);
610
977
  cachedAnalysis = result;
611
978
  isAnalyzing = false;
612
979
  subscribers.forEach(cb => cb(result));
@@ -624,5 +991,9 @@ const analyzeHostAppBundle = async () => {
624
991
  return result;
625
992
  };
626
993
  exports.analyzeHostAppBundle = analyzeHostAppBundle;
994
+ const clearCachedBundleAnalysis = () => {
995
+ cachedAnalysis = null;
996
+ };
997
+ exports.clearCachedBundleAnalysis = clearCachedBundleAnalysis;
627
998
  const getCachedBundleAnalysis = () => cachedAnalysis;
628
999
  exports.getCachedBundleAnalysis = getCachedBundleAnalysis;