react-native-inapp-inspector 1.1.24 → 1.1.26

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