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.
- package/dist/commonjs/components/AnalyticsEventCard.js +94 -88
- package/dist/commonjs/components/ConsoleLogCard.js +49 -25
- package/dist/commonjs/components/Inspector/AnalyticsFilterModal.d.ts +7 -0
- package/dist/commonjs/components/Inspector/AnalyticsFilterModal.js +1002 -0
- package/dist/commonjs/components/Inspector/AnalyticsTab.js +637 -10
- package/dist/commonjs/components/Inspector/BundleTab.d.ts +1 -1
- package/dist/commonjs/components/Inspector/BundleTab.js +859 -402
- package/dist/commonjs/components/Inspector/InspectorContext.js +6 -1
- package/dist/commonjs/components/Inspector/InspectorHeader.js +13 -5
- package/dist/commonjs/components/Inspector/LogDetail.js +223 -111
- package/dist/commonjs/components/Inspector/MainScreen.js +1 -1
- package/dist/commonjs/components/Inspector/NetworkDetail.js +131 -81
- package/dist/commonjs/components/Inspector/PerformanceTab.js +20 -10
- package/dist/commonjs/components/NetworkIcons.d.ts +14 -0
- package/dist/commonjs/components/NetworkIcons.js +93 -1
- package/dist/commonjs/components/SegmentedTabs.js +31 -22
- package/dist/commonjs/components/TreeNode.js +13 -12
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/customHooks/bundleAnalyzer.d.ts +6 -2
- package/dist/commonjs/customHooks/bundleAnalyzer.js +552 -114
- package/dist/commonjs/customHooks/consoleLogger.js +83 -45
- package/dist/commonjs/customHooks/networkLogger.js +10 -1
- package/dist/commonjs/customHooks/performanceTracker.d.ts +8 -0
- package/dist/commonjs/customHooks/performanceTracker.js +44 -3
- package/dist/commonjs/decorators/index.d.ts +49 -0
- package/dist/commonjs/decorators/index.js +142 -0
- package/dist/commonjs/helpers/gaAnalyticsRegistry.d.ts +18 -0
- package/dist/commonjs/helpers/gaAnalyticsRegistry.js +174 -0
- package/dist/commonjs/helpers/index.d.ts +7 -4
- package/dist/commonjs/helpers/index.js +102 -64
- package/dist/commonjs/i18n/locales/en.json +53 -4
- package/dist/commonjs/index.d.ts +4 -0
- package/dist/commonjs/index.js +216 -8
- package/dist/commonjs/styles/index.d.ts +4 -1
- package/dist/commonjs/styles/index.js +22 -19
- package/dist/commonjs/types/enums.d.ts +115 -9
- package/dist/commonjs/types/enums.js +103 -2
- package/dist/commonjs/types/interfaces.d.ts +19 -0
- package/dist/esm/components/AnalyticsEventCard.js +95 -89
- package/dist/esm/components/ConsoleLogCard.js +49 -25
- package/dist/esm/components/Inspector/AnalyticsFilterModal.d.ts +7 -0
- package/dist/esm/components/Inspector/AnalyticsFilterModal.js +964 -0
- package/dist/esm/components/Inspector/AnalyticsTab.js +639 -12
- package/dist/esm/components/Inspector/BundleTab.d.ts +1 -1
- package/dist/esm/components/Inspector/BundleTab.js +860 -403
- package/dist/esm/components/Inspector/InspectorContext.js +1 -0
- package/dist/esm/components/Inspector/InspectorHeader.js +14 -6
- package/dist/esm/components/Inspector/LogDetail.js +223 -111
- package/dist/esm/components/Inspector/MainScreen.js +2 -2
- package/dist/esm/components/Inspector/NetworkDetail.js +132 -49
- package/dist/esm/components/Inspector/PerformanceTab.js +20 -10
- package/dist/esm/components/NetworkIcons.d.ts +14 -0
- package/dist/esm/components/NetworkIcons.js +78 -0
- package/dist/esm/components/SegmentedTabs.js +31 -22
- package/dist/esm/components/TreeNode.js +13 -12
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/customHooks/bundleAnalyzer.d.ts +6 -2
- package/dist/esm/customHooks/bundleAnalyzer.js +549 -114
- package/dist/esm/customHooks/consoleLogger.js +83 -45
- package/dist/esm/customHooks/networkLogger.js +10 -1
- package/dist/esm/customHooks/performanceTracker.d.ts +8 -0
- package/dist/esm/customHooks/performanceTracker.js +38 -1
- package/dist/esm/decorators/index.d.ts +49 -0
- package/dist/esm/decorators/index.js +137 -0
- package/dist/esm/helpers/gaAnalyticsRegistry.d.ts +18 -0
- package/dist/esm/helpers/gaAnalyticsRegistry.js +169 -0
- package/dist/esm/helpers/index.d.ts +7 -4
- package/dist/esm/helpers/index.js +100 -63
- package/dist/esm/i18n/locales/en.json +53 -4
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +190 -8
- package/dist/esm/styles/index.d.ts +4 -1
- package/dist/esm/styles/index.js +22 -19
- package/dist/esm/types/enums.d.ts +115 -9
- package/dist/esm/types/enums.js +102 -3
- package/dist/esm/types/interfaces.d.ts +19 -0
- package/package.json +1 -1
|
@@ -8,99 +8,536 @@ import { AppColors } from '../styles/AppColors';
|
|
|
8
8
|
// 3. Extracting real module paths (project files vs node_modules dependencies).
|
|
9
9
|
// 4. Computing real Development and Production Binary (.ipa / .aab / .apk) sizes.
|
|
10
10
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
-
import { NativeModules, Platform } from 'react-native';
|
|
11
|
+
import { NativeModules, Platform, TurboModuleRegistry } from 'react-native';
|
|
12
12
|
let cachedAnalysis = null;
|
|
13
13
|
let isAnalyzing = false;
|
|
14
14
|
const subscribers = [];
|
|
15
|
+
const getSourceCodeModule = () => {
|
|
16
|
+
try {
|
|
17
|
+
const legacy = NativeModules?.SourceCode;
|
|
18
|
+
if (legacy && typeof legacy.scriptURL === 'string' && legacy.scriptURL.length > 0) {
|
|
19
|
+
return legacy;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch { }
|
|
23
|
+
try {
|
|
24
|
+
const turbo = TurboModuleRegistry?.get?.('SourceCode');
|
|
25
|
+
const constants = turbo?.getConstants?.();
|
|
26
|
+
const scriptURL = turbo?.scriptURL || constants?.scriptURL;
|
|
27
|
+
if (typeof scriptURL === 'string' && scriptURL.length > 0) {
|
|
28
|
+
return { scriptURL };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch { }
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
15
34
|
export const getHostScriptURL = () => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
35
|
+
// 1. NativeModules.SourceCode (bridged) or SourceCode TurboModule (bridgeless / new arch)
|
|
36
|
+
try {
|
|
37
|
+
const scriptURL = getSourceCodeModule()?.scriptURL;
|
|
38
|
+
if (typeof scriptURL === 'string' && scriptURL.length > 0) {
|
|
39
|
+
return scriptURL;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch { }
|
|
43
|
+
// 2. Android legacy & new arch: PlatformConstants.serverHost, DevSettings, or AndroidConstants
|
|
44
|
+
try {
|
|
45
|
+
const serverHost = NativeModules?.PlatformConstants?.serverHost ||
|
|
46
|
+
NativeModules?.AndroidConstants?.serverHost ||
|
|
47
|
+
NativeModules?.DevSettings?.serverHost ||
|
|
48
|
+
NativeModules?.DevSettings?.getConstants?.()?.serverHost;
|
|
49
|
+
if (typeof serverHost === 'string' && serverHost.length > 0) {
|
|
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`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch { }
|
|
66
|
+
return '';
|
|
67
|
+
};
|
|
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];
|
|
70
|
+
const buildProbeUrls = (scriptURL) => {
|
|
71
|
+
const urls = [];
|
|
72
|
+
let extractedHost = null;
|
|
73
|
+
let extractedPort = null;
|
|
74
|
+
if (typeof scriptURL === 'string' && scriptURL.startsWith('http')) {
|
|
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
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Prioritize dynamically extracted port first
|
|
85
|
+
const ports = extractedPort
|
|
86
|
+
? Array.from(new Set([extractedPort, ...DEV_SERVER_PORTS]))
|
|
87
|
+
: DEV_SERVER_PORTS;
|
|
88
|
+
const hosts = Platform.OS === 'android'
|
|
89
|
+
? ['localhost', '127.0.0.1', '10.0.2.2', '10.0.3.2']
|
|
90
|
+
: ['localhost', '127.0.0.1'];
|
|
91
|
+
if (extractedHost && !hosts.includes(extractedHost)) {
|
|
92
|
+
hosts.unshift(extractedHost);
|
|
93
|
+
}
|
|
94
|
+
for (const port of ports) {
|
|
95
|
+
for (const host of hosts) {
|
|
96
|
+
urls.push(`http://${host}:${port}/index.bundle?platform=${Platform.OS}&dev=true`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return Array.from(new Set(urls));
|
|
100
|
+
};
|
|
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
|
+
};
|
|
138
|
+
try {
|
|
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);
|
|
221
|
+
};
|
|
222
|
+
Object.defineProperty(tracked, '__inspectorTracked', { value: true, enumerable: false });
|
|
223
|
+
g.__r = tracked;
|
|
224
|
+
}
|
|
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;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
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));
|
|
19
318
|
}
|
|
20
|
-
return
|
|
21
|
-
|
|
22
|
-
|
|
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' };
|
|
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'));
|
|
23
383
|
};
|
|
24
384
|
/**
|
|
25
385
|
* Parses raw Metro bundle source code to discover real modules and packages in the host app.
|
|
26
386
|
*/
|
|
27
|
-
export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
387
|
+
export const parseBundleSource = (bundleText, totalBytes, scriptURL, isLive = true) => {
|
|
28
388
|
const isHermes = Boolean(globalThis.HermesInternal);
|
|
29
389
|
const totalDevMb = Number((totalBytes / (1024 * 1024)).toFixed(2));
|
|
30
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
|
+
}
|
|
31
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));
|
|
32
456
|
const discoveredFiles = [];
|
|
33
|
-
// Match module declarations: __d(function(...), id, [...], "path/to/module.js") or comments
|
|
34
|
-
// In Metro: __d(function(...), 42, [1, 2], "node_modules/lodash/index.js")
|
|
35
|
-
const modulePathRegex = /(?:__d\s*\([^,]+,[^,]+,[^,]+,["']([^"']+)["']|\/\/\s*@metro-module-path\s+([^\n\r]+)|["']((?:node_modules|\.|\/|[a-zA-Z0-9_-]+)\/[^"']+\.[a-zA-Z0-9]+)["'])/g;
|
|
36
|
-
let match;
|
|
37
457
|
let fileIdx = 0;
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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)
|
|
43
493
|
continue;
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
discoveredPackagesMap.set(pkgName, (discoveredPackagesMap.get(pkgName) || 0) + 1);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
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;
|
|
54
501
|
}
|
|
55
|
-
else if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
category = 'typescript';
|
|
70
|
-
color = AppColors.sky500;
|
|
71
|
-
}
|
|
72
|
-
else if (ext === 'PNG' || ext === 'JPG' || ext === 'SVG') {
|
|
73
|
-
category = 'image';
|
|
74
|
-
color = AppColors.pink500;
|
|
75
|
-
}
|
|
76
|
-
else if (ext === 'TTF' || ext === 'OTF') {
|
|
77
|
-
category = 'font';
|
|
78
|
-
color = AppColors.purple500;
|
|
79
|
-
}
|
|
80
|
-
else if (ext === 'JSON') {
|
|
81
|
-
category = 'json';
|
|
82
|
-
color = AppColors.emerald500;
|
|
83
|
-
}
|
|
84
|
-
// Approximate module size from total and count
|
|
85
|
-
const approxKb = Math.max(2, Math.round((totalDevKb * 0.15) / Math.max(seenPaths.size, 20)));
|
|
86
|
-
discoveredFiles.push({
|
|
87
|
-
id: `host-file-${fileIdx++}`,
|
|
88
|
-
name,
|
|
89
|
-
path: rawPath,
|
|
90
|
-
ext,
|
|
91
|
-
category,
|
|
92
|
-
sizeKb: approxKb,
|
|
93
|
-
meta: `Active Host App Module • ${category.toUpperCase()}`,
|
|
94
|
-
color,
|
|
95
|
-
status: 'optimal',
|
|
96
|
-
advice: 'Bundled into Host App Development Runtime',
|
|
97
|
-
isConsumed: true,
|
|
98
|
-
});
|
|
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;
|
|
99
516
|
}
|
|
100
|
-
if (
|
|
101
|
-
|
|
517
|
+
else if (ext === 'JSON' || ext === 'CSS' || ext === 'SCSS' || ext === 'HTML' || ext === 'MD' || ext === 'GRAPHQL' || ext === 'GQL') {
|
|
518
|
+
category = 'json';
|
|
519
|
+
color = AppColors.emerald500;
|
|
520
|
+
}
|
|
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
|
+
});
|
|
102
539
|
}
|
|
103
|
-
// If no
|
|
540
|
+
// If no modules could be parsed (e.g. Hermes bytecode / obfuscated bundle), synthesize from loaded modules
|
|
104
541
|
if (discoveredFiles.length === 0) {
|
|
105
542
|
discoveredFiles.push({
|
|
106
543
|
id: 'hf-1',
|
|
@@ -289,7 +726,7 @@ export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
|
289
726
|
const iosComponents = [
|
|
290
727
|
{
|
|
291
728
|
id: 'ios-c1',
|
|
292
|
-
name: '
|
|
729
|
+
name: 'Frameworks & Dynamic Pods',
|
|
293
730
|
category: 'frameworks',
|
|
294
731
|
sizeMb: nativeFrameworksMb,
|
|
295
732
|
pct: Number(((nativeFrameworksMb / iosInstallMb) * 100).toFixed(1)),
|
|
@@ -299,7 +736,7 @@ export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
|
299
736
|
},
|
|
300
737
|
{
|
|
301
738
|
id: 'ios-c2',
|
|
302
|
-
name: '
|
|
739
|
+
name: 'Mach-O Executable (ARM64)',
|
|
303
740
|
category: 'native',
|
|
304
741
|
sizeMb: nativeMachoMb,
|
|
305
742
|
pct: Number(((nativeMachoMb / iosInstallMb) * 100).toFixed(1)),
|
|
@@ -309,7 +746,7 @@ export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
|
309
746
|
},
|
|
310
747
|
{
|
|
311
748
|
id: 'ios-c3',
|
|
312
|
-
name: 'Asset Catalog (Assets.car
|
|
749
|
+
name: 'Asset Catalog (Assets.car)',
|
|
313
750
|
category: 'assets',
|
|
314
751
|
sizeMb: assetsCatalogMb,
|
|
315
752
|
pct: Number(((assetsCatalogMb / iosInstallMb) * 100).toFixed(1)),
|
|
@@ -319,7 +756,7 @@ export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
|
319
756
|
},
|
|
320
757
|
{
|
|
321
758
|
id: 'ios-c4',
|
|
322
|
-
name: 'Hermes Bytecode (main.jsbundle
|
|
759
|
+
name: 'Hermes Bytecode (main.jsbundle)',
|
|
323
760
|
category: 'js',
|
|
324
761
|
sizeMb: releaseJsMb,
|
|
325
762
|
pct: Number(((releaseJsMb / iosInstallMb) * 100).toFixed(1)),
|
|
@@ -329,7 +766,7 @@ export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
|
329
766
|
},
|
|
330
767
|
{
|
|
331
768
|
id: 'ios-c5',
|
|
332
|
-
name: '
|
|
769
|
+
name: 'Metadata & Code Signatures',
|
|
333
770
|
category: 'meta',
|
|
334
771
|
sizeMb: metadataMb,
|
|
335
772
|
pct: Number(((metadataMb / iosInstallMb) * 100).toFixed(1)),
|
|
@@ -341,7 +778,7 @@ export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
|
341
778
|
const androidComponents = [
|
|
342
779
|
{
|
|
343
780
|
id: 'and-c1',
|
|
344
|
-
name: 'Native C++
|
|
781
|
+
name: 'Native C++ Libraries (.so)',
|
|
345
782
|
category: 'native',
|
|
346
783
|
sizeMb: androidCppMb,
|
|
347
784
|
pct: Number(((androidCppMb / androidInstallMb) * 100).toFixed(1)),
|
|
@@ -351,7 +788,7 @@ export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
|
351
788
|
},
|
|
352
789
|
{
|
|
353
790
|
id: 'and-c2',
|
|
354
|
-
name: 'Compiled DEX
|
|
791
|
+
name: 'Compiled DEX (classes.dex)',
|
|
355
792
|
category: 'frameworks',
|
|
356
793
|
sizeMb: androidDexMb,
|
|
357
794
|
pct: Number(((androidDexMb / androidInstallMb) * 100).toFixed(1)),
|
|
@@ -361,7 +798,7 @@ export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
|
361
798
|
},
|
|
362
799
|
{
|
|
363
800
|
id: 'and-c3',
|
|
364
|
-
name: 'Android Resources
|
|
801
|
+
name: 'Android Resources (res/)',
|
|
365
802
|
category: 'assets',
|
|
366
803
|
sizeMb: androidResMb,
|
|
367
804
|
pct: Number(((androidResMb / androidInstallMb) * 100).toFixed(1)),
|
|
@@ -371,7 +808,7 @@ export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
|
371
808
|
},
|
|
372
809
|
{
|
|
373
810
|
id: 'and-c4',
|
|
374
|
-
name: 'Hermes Bytecode (
|
|
811
|
+
name: 'Hermes Bytecode (Android)',
|
|
375
812
|
category: 'js',
|
|
376
813
|
sizeMb: releaseJsMb,
|
|
377
814
|
pct: Number(((releaseJsMb / androidInstallMb) * 100).toFixed(1)),
|
|
@@ -381,7 +818,7 @@ export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
|
381
818
|
},
|
|
382
819
|
{
|
|
383
820
|
id: 'and-c5',
|
|
384
|
-
name: '
|
|
821
|
+
name: 'Manifest & Signatures (META-INF)',
|
|
385
822
|
category: 'meta',
|
|
386
823
|
sizeMb: 2.6,
|
|
387
824
|
pct: Number(((2.6 / androidInstallMb) * 100).toFixed(1)),
|
|
@@ -397,7 +834,7 @@ export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
|
397
834
|
const androidApkComponents = [
|
|
398
835
|
{
|
|
399
836
|
id: 'apk-c1',
|
|
400
|
-
name: 'Multi-ABI
|
|
837
|
+
name: 'Multi-ABI C++ Libraries (.so)',
|
|
401
838
|
category: 'native',
|
|
402
839
|
sizeMb: androidMultiAbiCppMb,
|
|
403
840
|
pct: Number(((androidMultiAbiCppMb / androidApkInstallMb) * 100).toFixed(1)),
|
|
@@ -417,7 +854,7 @@ export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
|
417
854
|
},
|
|
418
855
|
{
|
|
419
856
|
id: 'apk-c3',
|
|
420
|
-
name: 'Android Resources & Assets (res
|
|
857
|
+
name: 'Android Resources & Assets (res/)',
|
|
421
858
|
category: 'assets',
|
|
422
859
|
sizeMb: androidResMb,
|
|
423
860
|
pct: Number(((androidResMb / androidApkInstallMb) * 100).toFixed(1)),
|
|
@@ -427,7 +864,7 @@ export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
|
427
864
|
},
|
|
428
865
|
{
|
|
429
866
|
id: 'apk-c4',
|
|
430
|
-
name: 'Hermes Bytecode Bundle (
|
|
867
|
+
name: 'Hermes Bytecode Bundle (Android)',
|
|
431
868
|
category: 'js',
|
|
432
869
|
sizeMb: releaseJsMb,
|
|
433
870
|
pct: Number(((releaseJsMb / androidApkInstallMb) * 100).toFixed(1)),
|
|
@@ -437,7 +874,7 @@ export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
|
437
874
|
},
|
|
438
875
|
{
|
|
439
876
|
id: 'apk-c5',
|
|
440
|
-
name: '
|
|
877
|
+
name: 'Manifest & Signatures (META-INF)',
|
|
441
878
|
category: 'meta',
|
|
442
879
|
sizeMb: 3.2,
|
|
443
880
|
pct: Number(((3.2 / androidApkInstallMb) * 100).toFixed(1)),
|
|
@@ -447,13 +884,13 @@ export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
|
447
884
|
},
|
|
448
885
|
];
|
|
449
886
|
return {
|
|
450
|
-
isLive
|
|
887
|
+
isLive,
|
|
451
888
|
scriptURL,
|
|
452
889
|
totalDevBytes: totalBytes,
|
|
453
890
|
totalDevMb,
|
|
454
891
|
totalDevKb,
|
|
455
892
|
isHermes,
|
|
456
|
-
moduleCount:
|
|
893
|
+
moduleCount: modules.length || discoveredFiles.length + packagesList.length,
|
|
457
894
|
packageCount: packagesList.length,
|
|
458
895
|
filesCount: discoveredFiles.length,
|
|
459
896
|
splitUp: {
|
|
@@ -510,9 +947,13 @@ export const parseBundleSource = (bundleText, totalBytes, scriptURL) => {
|
|
|
510
947
|
};
|
|
511
948
|
/**
|
|
512
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.
|
|
513
951
|
*/
|
|
514
|
-
export const analyzeHostAppBundle = async () => {
|
|
515
|
-
if (
|
|
952
|
+
export const analyzeHostAppBundle = async (forceRefresh = false) => {
|
|
953
|
+
if (forceRefresh) {
|
|
954
|
+
cachedAnalysis = null;
|
|
955
|
+
}
|
|
956
|
+
if (cachedAnalysis && !forceRefresh)
|
|
516
957
|
return cachedAnalysis;
|
|
517
958
|
if (isAnalyzing) {
|
|
518
959
|
return new Promise(resolve => {
|
|
@@ -521,34 +962,28 @@ export const analyzeHostAppBundle = async () => {
|
|
|
521
962
|
}
|
|
522
963
|
isAnalyzing = true;
|
|
523
964
|
const scriptURL = getHostScriptURL();
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const result = parseBundleSource(bundleText, byteLength, scriptURL);
|
|
535
|
-
cachedAnalysis = result;
|
|
536
|
-
isAnalyzing = false;
|
|
537
|
-
subscribers.forEach(cb => cb(result));
|
|
538
|
-
subscribers.length = 0;
|
|
539
|
-
return result;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
catch (err) {
|
|
543
|
-
// If fetch failed (e.g. standalone production build or offline)
|
|
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);
|
|
970
|
+
cachedAnalysis = result;
|
|
971
|
+
isAnalyzing = false;
|
|
972
|
+
subscribers.forEach(cb => cb(result));
|
|
973
|
+
subscribers.length = 0;
|
|
974
|
+
return result;
|
|
544
975
|
}
|
|
545
|
-
// Fallback
|
|
976
|
+
// Fallback: could not reach a Metro dev server (release build, offline, or
|
|
977
|
+
// custom dev-server host). Return estimated values so the UI never crashes.
|
|
546
978
|
const fallbackBytes = 6840000; // ~6.8MB standard RN dev bundle
|
|
547
|
-
const result = parseBundleSource('', fallbackBytes, scriptURL);
|
|
979
|
+
const result = parseBundleSource('', fallbackBytes, scriptURL || 'unknown', false);
|
|
548
980
|
cachedAnalysis = result;
|
|
549
981
|
isAnalyzing = false;
|
|
550
982
|
subscribers.forEach(cb => cb(result));
|
|
551
983
|
subscribers.length = 0;
|
|
552
984
|
return result;
|
|
553
985
|
};
|
|
986
|
+
export const clearCachedBundleAnalysis = () => {
|
|
987
|
+
cachedAnalysis = null;
|
|
988
|
+
};
|
|
554
989
|
export const getCachedBundleAnalysis = () => cachedAnalysis;
|