react-native-inapp-inspector 1.1.23 → 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 +859 -402
  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 +6 -2
  21. package/dist/commonjs/customHooks/bundleAnalyzer.js +552 -114
  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 +53 -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 +860 -403
  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 +6 -2
  60. package/dist/esm/customHooks/bundleAnalyzer.js +549 -114
  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 +53 -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
  //
@@ -15,96 +15,535 @@ const react_native_1 = require("react-native");
15
15
  let cachedAnalysis = null;
16
16
  let isAnalyzing = false;
17
17
  const subscribers = [];
18
+ const getSourceCodeModule = () => {
19
+ try {
20
+ const legacy = react_native_1.NativeModules?.SourceCode;
21
+ if (legacy && typeof legacy.scriptURL === 'string' && legacy.scriptURL.length > 0) {
22
+ return legacy;
23
+ }
24
+ }
25
+ catch { }
26
+ try {
27
+ const turbo = react_native_1.TurboModuleRegistry?.get?.('SourceCode');
28
+ const constants = turbo?.getConstants?.();
29
+ const scriptURL = turbo?.scriptURL || constants?.scriptURL;
30
+ if (typeof scriptURL === 'string' && scriptURL.length > 0) {
31
+ return { scriptURL };
32
+ }
33
+ }
34
+ catch { }
35
+ return null;
36
+ };
18
37
  const getHostScriptURL = () => {
19
- const SourceCode = react_native_1.NativeModules?.SourceCode;
20
- if (SourceCode && typeof SourceCode.scriptURL === 'string' && SourceCode.scriptURL.length > 0) {
21
- return SourceCode.scriptURL;
38
+ // 1. NativeModules.SourceCode (bridged) or SourceCode TurboModule (bridgeless / new arch)
39
+ try {
40
+ const scriptURL = getSourceCodeModule()?.scriptURL;
41
+ if (typeof scriptURL === 'string' && scriptURL.length > 0) {
42
+ return scriptURL;
43
+ }
44
+ }
45
+ catch { }
46
+ // 2. Android legacy & new arch: PlatformConstants.serverHost, DevSettings, or AndroidConstants
47
+ try {
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;
52
+ if (typeof serverHost === 'string' && serverHost.length > 0) {
53
+ const formattedHost = serverHost.startsWith('http') ? serverHost : `http://${serverHost}`;
54
+ return `${formattedHost}/index.bundle?platform=${react_native_1.Platform.OS}&dev=true`;
55
+ }
22
56
  }
23
- return react_native_1.Platform.OS === 'ios'
24
- ? 'http://localhost:8081/index.bundle?platform=ios&dev=true'
25
- : 'http://10.0.2.2:8081/index.bundle?platform=android&dev=true';
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`;
66
+ }
67
+ }
68
+ catch { }
69
+ return '';
26
70
  };
27
71
  exports.getHostScriptURL = getHostScriptURL;
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];
74
+ const buildProbeUrls = (scriptURL) => {
75
+ const urls = [];
76
+ let extractedHost = null;
77
+ let extractedPort = null;
78
+ if (typeof scriptURL === 'string' && scriptURL.startsWith('http')) {
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
+ }
87
+ }
88
+ // Prioritize dynamically extracted port first
89
+ const ports = extractedPort
90
+ ? Array.from(new Set([extractedPort, ...DEV_SERVER_PORTS]))
91
+ : DEV_SERVER_PORTS;
92
+ const hosts = react_native_1.Platform.OS === 'android'
93
+ ? ['localhost', '127.0.0.1', '10.0.2.2', '10.0.3.2']
94
+ : ['localhost', '127.0.0.1'];
95
+ if (extractedHost && !hosts.includes(extractedHost)) {
96
+ hosts.unshift(extractedHost);
97
+ }
98
+ for (const port of ports) {
99
+ for (const host of hosts) {
100
+ urls.push(`http://${host}:${port}/index.bundle?platform=${react_native_1.Platform.OS}&dev=true`);
101
+ }
102
+ }
103
+ return Array.from(new Set(urls));
104
+ };
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
+ };
142
+ try {
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);
225
+ };
226
+ Object.defineProperty(tracked, '__inspectorTracked', { value: true, enumerable: false });
227
+ g.__r = tracked;
228
+ }
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;
246
+ }
247
+ }
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' };
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'));
389
+ };
28
390
  /**
29
391
  * Parses raw Metro bundle source code to discover real modules and packages in the host app.
30
392
  */
31
- const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
393
+ const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) => {
32
394
  const isHermes = Boolean(globalThis.HermesInternal);
33
395
  const totalDevMb = Number((totalBytes / (1024 * 1024)).toFixed(2));
34
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
+ }
35
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));
36
462
  const discoveredFiles = [];
37
- // Match module declarations: __d(function(...), id, [...], "path/to/module.js") or comments
38
- // In Metro: __d(function(...), 42, [1, 2], "node_modules/lodash/index.js")
39
- const modulePathRegex = /(?:__d\s*\([^,]+,[^,]+,[^,]+,["']([^"']+)["']|\/\/\s*@metro-module-path\s+([^\n\r]+)|["']((?:node_modules|\.|\/|[a-zA-Z0-9_-]+)\/[^"']+\.[a-zA-Z0-9]+)["'])/g;
40
- let match;
41
463
  let fileIdx = 0;
42
- const seenPaths = new Set();
43
- // Extract from text
44
- while ((match = modulePathRegex.exec(bundleText)) !== null) {
45
- const rawPath = match[1] || match[2] || match[3];
46
- 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)
47
499
  continue;
48
- seenPaths.add(rawPath);
49
- const isNodeModule = rawPath.includes('node_modules/');
50
- if (isNodeModule) {
51
- const pkgMatch = rawPath.match(/node_modules\/(?:@([^/]+)\/([^/]+)|([^/]+))/);
52
- if (pkgMatch) {
53
- const pkgName = pkgMatch[1] && pkgMatch[2] ? `@${pkgMatch[1]}/${pkgMatch[2]}` : pkgMatch[3];
54
- if (pkgName && !pkgName.startsWith('.')) {
55
- discoveredPackagesMap.set(pkgName, (discoveredPackagesMap.get(pkgName) || 0) + 1);
56
- }
57
- }
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;
58
507
  }
59
- else if (rawPath.endsWith('.tsx') ||
60
- rawPath.endsWith('.ts') ||
61
- rawPath.endsWith('.jsx') ||
62
- rawPath.endsWith('.js') ||
63
- rawPath.endsWith('.json') ||
64
- rawPath.endsWith('.png') ||
65
- rawPath.endsWith('.jpg') ||
66
- rawPath.endsWith('.svg') ||
67
- rawPath.endsWith('.ttf')) {
68
- const ext = rawPath.split('.').pop()?.toUpperCase() || 'JS';
69
- const name = rawPath.split('/').pop() || rawPath;
70
- let category = 'javascript';
71
- let color = AppColors_1.AppColors.indigo500;
72
- if (ext === 'TSX' || ext === 'TS') {
73
- category = 'typescript';
74
- color = AppColors_1.AppColors.sky500;
75
- }
76
- else if (ext === 'PNG' || ext === 'JPG' || ext === 'SVG') {
77
- category = 'image';
78
- color = AppColors_1.AppColors.pink500;
79
- }
80
- else if (ext === 'TTF' || ext === 'OTF') {
81
- category = 'font';
82
- color = AppColors_1.AppColors.purple500;
83
- }
84
- else if (ext === 'JSON') {
85
- category = 'json';
86
- color = AppColors_1.AppColors.emerald500;
87
- }
88
- // Approximate module size from total and count
89
- const approxKb = Math.max(2, Math.round((totalDevKb * 0.15) / Math.max(seenPaths.size, 20)));
90
- discoveredFiles.push({
91
- id: `host-file-${fileIdx++}`,
92
- name,
93
- path: rawPath,
94
- ext,
95
- category,
96
- sizeKb: approxKb,
97
- meta: `Active Host App Module • ${category.toUpperCase()}`,
98
- color,
99
- status: 'optimal',
100
- advice: 'Bundled into Host App Development Runtime',
101
- isConsumed: true,
102
- });
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;
103
522
  }
104
- if (seenPaths.size >= 1200)
105
- break; // Limit parsing overhead
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;
526
+ }
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
+ });
106
545
  }
107
- // 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
108
547
  if (discoveredFiles.length === 0) {
109
548
  discoveredFiles.push({
110
549
  id: 'hf-1',
@@ -293,7 +732,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
293
732
  const iosComponents = [
294
733
  {
295
734
  id: 'ios-c1',
296
- name: 'Dynamic Frameworks & Pods',
735
+ name: 'Frameworks & Dynamic Pods',
297
736
  category: 'frameworks',
298
737
  sizeMb: nativeFrameworksMb,
299
738
  pct: Number(((nativeFrameworksMb / iosInstallMb) * 100).toFixed(1)),
@@ -303,7 +742,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
303
742
  },
304
743
  {
305
744
  id: 'ios-c2',
306
- name: 'Native Mach-O Executable (ARM64)',
745
+ name: 'Mach-O Executable (ARM64)',
307
746
  category: 'native',
308
747
  sizeMb: nativeMachoMb,
309
748
  pct: Number(((nativeMachoMb / iosInstallMb) * 100).toFixed(1)),
@@ -313,7 +752,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
313
752
  },
314
753
  {
315
754
  id: 'ios-c3',
316
- name: 'Asset Catalog (Assets.car & Media)',
755
+ name: 'Asset Catalog (Assets.car)',
317
756
  category: 'assets',
318
757
  sizeMb: assetsCatalogMb,
319
758
  pct: Number(((assetsCatalogMb / iosInstallMb) * 100).toFixed(1)),
@@ -323,7 +762,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
323
762
  },
324
763
  {
325
764
  id: 'ios-c4',
326
- name: 'Hermes Bytecode (main.jsbundle / .hbc)',
765
+ name: 'Hermes Bytecode (main.jsbundle)',
327
766
  category: 'js',
328
767
  sizeMb: releaseJsMb,
329
768
  pct: Number(((releaseJsMb / iosInstallMb) * 100).toFixed(1)),
@@ -333,7 +772,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
333
772
  },
334
773
  {
335
774
  id: 'ios-c5',
336
- name: 'App Metadata & Code Signatures',
775
+ name: 'Metadata & Code Signatures',
337
776
  category: 'meta',
338
777
  sizeMb: metadataMb,
339
778
  pct: Number(((metadataMb / iosInstallMb) * 100).toFixed(1)),
@@ -345,7 +784,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
345
784
  const androidComponents = [
346
785
  {
347
786
  id: 'and-c1',
348
- name: 'Native C++ Shared Libraries (lib/arm64-v8a/)',
787
+ name: 'Native C++ Libraries (.so)',
349
788
  category: 'native',
350
789
  sizeMb: androidCppMb,
351
790
  pct: Number(((androidCppMb / androidInstallMb) * 100).toFixed(1)),
@@ -355,7 +794,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
355
794
  },
356
795
  {
357
796
  id: 'and-c2',
358
- name: 'Compiled DEX Bytecode (classes.dex)',
797
+ name: 'Compiled DEX (classes.dex)',
359
798
  category: 'frameworks',
360
799
  sizeMb: androidDexMb,
361
800
  pct: Number(((androidDexMb / androidInstallMb) * 100).toFixed(1)),
@@ -365,7 +804,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
365
804
  },
366
805
  {
367
806
  id: 'and-c3',
368
- name: 'Android Resources & Drawables (res/)',
807
+ name: 'Android Resources (res/)',
369
808
  category: 'assets',
370
809
  sizeMb: androidResMb,
371
810
  pct: Number(((androidResMb / androidInstallMb) * 100).toFixed(1)),
@@ -375,7 +814,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
375
814
  },
376
815
  {
377
816
  id: 'and-c4',
378
- name: 'Hermes Bytecode (index.android.bundle)',
817
+ name: 'Hermes Bytecode (Android)',
379
818
  category: 'js',
380
819
  sizeMb: releaseJsMb,
381
820
  pct: Number(((releaseJsMb / androidInstallMb) * 100).toFixed(1)),
@@ -385,7 +824,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
385
824
  },
386
825
  {
387
826
  id: 'and-c5',
388
- name: 'Android Manifest & Signatures (META-INF/)',
827
+ name: 'Manifest & Signatures (META-INF)',
389
828
  category: 'meta',
390
829
  sizeMb: 2.6,
391
830
  pct: Number(((2.6 / androidInstallMb) * 100).toFixed(1)),
@@ -401,7 +840,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
401
840
  const androidApkComponents = [
402
841
  {
403
842
  id: 'apk-c1',
404
- name: 'Multi-ABI Native C++ Libraries (arm64, v7a, x86_64)',
843
+ name: 'Multi-ABI C++ Libraries (.so)',
405
844
  category: 'native',
406
845
  sizeMb: androidMultiAbiCppMb,
407
846
  pct: Number(((androidMultiAbiCppMb / androidApkInstallMb) * 100).toFixed(1)),
@@ -421,7 +860,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
421
860
  },
422
861
  {
423
862
  id: 'apk-c3',
424
- name: 'Android Resources & Assets (res/, assets/)',
863
+ name: 'Android Resources & Assets (res/)',
425
864
  category: 'assets',
426
865
  sizeMb: androidResMb,
427
866
  pct: Number(((androidResMb / androidApkInstallMb) * 100).toFixed(1)),
@@ -431,7 +870,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
431
870
  },
432
871
  {
433
872
  id: 'apk-c4',
434
- name: 'Hermes Bytecode Bundle (index.android.bundle)',
873
+ name: 'Hermes Bytecode Bundle (Android)',
435
874
  category: 'js',
436
875
  sizeMb: releaseJsMb,
437
876
  pct: Number(((releaseJsMb / androidApkInstallMb) * 100).toFixed(1)),
@@ -441,7 +880,7 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
441
880
  },
442
881
  {
443
882
  id: 'apk-c5',
444
- name: 'Android Manifest & v1/v2/v3 Signatures (META-INF/)',
883
+ name: 'Manifest & Signatures (META-INF)',
445
884
  category: 'meta',
446
885
  sizeMb: 3.2,
447
886
  pct: Number(((3.2 / androidApkInstallMb) * 100).toFixed(1)),
@@ -451,13 +890,13 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
451
890
  },
452
891
  ];
453
892
  return {
454
- isLive: true,
893
+ isLive,
455
894
  scriptURL,
456
895
  totalDevBytes: totalBytes,
457
896
  totalDevMb,
458
897
  totalDevKb,
459
898
  isHermes,
460
- moduleCount: seenPaths.size || discoveredFiles.length + packagesList.length,
899
+ moduleCount: modules.length || discoveredFiles.length + packagesList.length,
461
900
  packageCount: packagesList.length,
462
901
  filesCount: discoveredFiles.length,
463
902
  splitUp: {
@@ -515,9 +954,13 @@ const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
515
954
  exports.parseBundleSource = parseBundleSource;
516
955
  /**
517
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.
518
958
  */
519
- const analyzeHostAppBundle = async () => {
520
- if (cachedAnalysis)
959
+ const analyzeHostAppBundle = async (forceRefresh = false) => {
960
+ if (forceRefresh) {
961
+ cachedAnalysis = null;
962
+ }
963
+ if (cachedAnalysis && !forceRefresh)
521
964
  return cachedAnalysis;
522
965
  if (isAnalyzing) {
523
966
  return new Promise(resolve => {
@@ -526,30 +969,21 @@ const analyzeHostAppBundle = async () => {
526
969
  }
527
970
  isAnalyzing = true;
528
971
  const scriptURL = (0, exports.getHostScriptURL)();
529
- try {
530
- if (scriptURL && scriptURL.startsWith('http')) {
531
- // 1. Try HEAD request first for fast content-length
532
- const headRes = await fetch(scriptURL, { method: 'HEAD' });
533
- const cl = headRes.headers.get('content-length');
534
- let byteLength = cl ? parseInt(cl, 10) : 0;
535
- // 2. Fetch partial text to discover real modules
536
- const getRes = await fetch(scriptURL);
537
- const bundleText = await getRes.text();
538
- byteLength = byteLength || bundleText.length;
539
- const result = (0, exports.parseBundleSource)(bundleText, byteLength, scriptURL);
540
- cachedAnalysis = result;
541
- isAnalyzing = false;
542
- subscribers.forEach(cb => cb(result));
543
- subscribers.length = 0;
544
- return result;
545
- }
546
- }
547
- catch (err) {
548
- // If fetch failed (e.g. standalone production build or offline)
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);
977
+ cachedAnalysis = result;
978
+ isAnalyzing = false;
979
+ subscribers.forEach(cb => cb(result));
980
+ subscribers.length = 0;
981
+ return result;
549
982
  }
550
- // Fallback to runtime memory estimation
983
+ // Fallback: could not reach a Metro dev server (release build, offline, or
984
+ // custom dev-server host). Return estimated values so the UI never crashes.
551
985
  const fallbackBytes = 6840000; // ~6.8MB standard RN dev bundle
552
- const result = (0, exports.parseBundleSource)('', fallbackBytes, scriptURL);
986
+ const result = (0, exports.parseBundleSource)('', fallbackBytes, scriptURL || 'unknown', false);
553
987
  cachedAnalysis = result;
554
988
  isAnalyzing = false;
555
989
  subscribers.forEach(cb => cb(result));
@@ -557,5 +991,9 @@ const analyzeHostAppBundle = async () => {
557
991
  return result;
558
992
  };
559
993
  exports.analyzeHostAppBundle = analyzeHostAppBundle;
994
+ const clearCachedBundleAnalysis = () => {
995
+ cachedAnalysis = null;
996
+ };
997
+ exports.clearCachedBundleAnalysis = clearCachedBundleAnalysis;
560
998
  const getCachedBundleAnalysis = () => cachedAnalysis;
561
999
  exports.getCachedBundleAnalysis = getCachedBundleAnalysis;