-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Expand file tree
/
Copy pathinspector_modules.ts
More file actions
234 lines (213 loc) · 6.89 KB
/
inspector_modules.ts
File metadata and controls
234 lines (213 loc) · 6.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
import './globals';
import './debugger/webinspector-network';
import './debugger/webinspector-dom';
import './debugger/webinspector-css';
import { File, knownFolders } from './file-system';
// import/destructure style helps commonjs/esm build issues
import * as sourceMapJs from 'source-map-js';
const { SourceMapConsumer } = sourceMapJs;
// note: bundlers can by default use 'source-map' files with runtimes v9+
// helps avoid having to decode the inline base64 source maps
const usingSourceMapFiles = true;
let loadedSourceMaps: Map<string, any>;
let consumerCache: Map<string, any>;
function getConsumer(mapPath: string, sourceMap: any) {
if (!consumerCache) {
consumerCache = new Map();
}
let c = consumerCache.get(mapPath);
if (!c) {
try {
c = new SourceMapConsumer(sourceMap);
consumerCache.set(mapPath, c);
} catch (error) {
// Keep quiet in production-like console; failures just fall back to original stack
console.debug && console.debug(`SourceMapConsumer failed for ${mapPath}:`, error);
return null;
}
}
return c;
}
function safeReadText(path: string): string | null {
try {
if (File.exists(path)) {
return File.fromPath(path).readTextSync();
}
} catch (_) {}
return null;
}
function findInlineOrLinkedMapFromJs(jsPath: string): { key: string; text: string } | null {
const jsText = safeReadText(jsPath);
if (!jsText) return null;
// Look for the last sourceMappingURL directive
// Supports both //# and /*# */ styles; capture up to line end or */
const re = /[#@]\s*sourceMappingURL=([^\s*]+)(?:\s*\*\/)?/g;
let match: RegExpExecArray | null = null;
let last: RegExpExecArray | null = null;
while ((match = re.exec(jsText))) last = match;
if (!last) return null;
const url = last[1];
if (url.startsWith('data:application/json')) {
const base64 = url.split(',')[1];
if (!base64) return null;
try {
const text = atob(base64);
return { key: `inline:${jsPath}`, text };
} catch (_) {
return null;
}
}
// Linked .map file (relative)
const jsDir = jsPath.substring(0, jsPath.lastIndexOf('/'));
const mapPath = `${jsDir}/${url}`;
const text = safeReadText(mapPath);
if (text) {
return { key: mapPath, text };
}
return null;
}
function loadAndExtractMap(mapPath: string, fallbackJsPath?: string) {
// check cache first
if (!loadedSourceMaps) {
loadedSourceMaps = new Map();
}
let mapText = loadedSourceMaps.get(mapPath);
if (mapText) {
return mapText; // already loaded
} else {
if (File.exists(mapPath)) {
try {
const contents = File.fromPath(mapPath).readTextSync();
// Note: we may want to do this, keeping for reference if needed in future.
// Check size before processing (skip very large source maps)
// const maxSizeBytes = 10 * 1024 * 1024; // 10MB limit
// if (contents.length > maxSizeBytes) {
// console.warn(`Source map ${mapPath} is too large (${contents.length} bytes), skipping...`);
// return null;
// }
if (usingSourceMapFiles) {
mapText = contents;
} else {
// parse out the inline base64
const match = contents.match(/\/\/[#@] sourceMappingURL=data:application\/json[^,]+,(.+)$/);
if (!match) {
console.warn(`Invalid source map format in ${mapPath}`);
return null;
}
const base64 = match[1];
const binary = atob(base64);
// this is the raw text of the source map
// seems to work without doing decodeURIComponent tricks
mapText = binary;
}
} catch (error) {
console.debug && console.debug(`Failed to load source map ${mapPath}:`, error);
return null;
}
} else {
// Try fallback: read inline or linked map from the JS file itself
if (fallbackJsPath) {
const alt = findInlineOrLinkedMapFromJs(fallbackJsPath);
if (alt && alt.text) {
mapText = alt.text;
// Cache under both the requested key and the alt key so future lookups are fast
loadedSourceMaps.set(alt.key, alt.text);
} else {
return null;
}
} else {
// no source maps
return null;
}
}
}
loadedSourceMaps.set(mapPath, mapText); // cache it
return mapText;
}
function remapFrame(file: string, line: number, column: number) {
/**
* bundlers can use source map files or inline.
* To use source map files, run with `--env.sourceMap=source-map`.
* Notes:
* Starting with @nativescript/webpack 5.0.25, `source-map` files are used by default when using runtimes v9+.
*/
const appPath = knownFolders.currentApp().path;
let sourceMapFileExt = '';
if (usingSourceMapFiles) {
sourceMapFileExt = '.map';
}
const rel = file.replace('file:///app/', '');
const jsPath = `${appPath}/${rel}`;
let mapPath = `${jsPath}${sourceMapFileExt}`; // default: same name + .map
// Fallback: if .mjs.map missing, try .js.map
if (!File.exists(mapPath) && rel.endsWith('.mjs')) {
const jsMapFallback = `${appPath}/${rel.replace(/\.mjs$/, '.js.map')}`;
if (File.exists(jsMapFallback)) {
mapPath = jsMapFallback;
}
}
const sourceMap = loadAndExtractMap(mapPath, jsPath);
if (!sourceMap) {
return { source: null, line: 0, column: 0 };
}
const consumer = getConsumer(mapPath, sourceMap);
if (!consumer) {
return { source: null, line: 0, column: 0 };
}
try {
return consumer.originalPositionFor({ line, column });
} catch (error) {
console.debug && console.debug(`Remap failed for ${file}:${line}:${column}:`, error);
return { source: null, line: 0, column: 0 };
}
}
function remapStack(raw: string): string {
const lines = raw.split('\n');
const out = lines.map((line) => {
// 1) Parenthesized frame: at fn (file:...:L:C)
let m = /\((.+):(\d+):(\d+)\)/.exec(line);
if (m) {
try {
const [_, file, l, c] = m;
const orig = remapFrame(file, +l, +c);
if (!orig.source) return line;
return line.replace(/\(.+\)/, `(${orig.source}:${orig.line}:${orig.column})`);
} catch (error) {
console.debug && console.debug('Remap failed for frame:', line, error);
return line;
}
}
// 2) Bare frame: at file:///app/vendor.js:L:C (no parentheses)
const bare = /(\s+at\s+)([^\s()]+):(\d+):(\d+)/.exec(line);
if (bare) {
try {
const [, prefix, file, l, c] = bare;
const orig = remapFrame(file, +l, +c);
if (!orig.source) return line;
const replacement = `${prefix}${orig.source}:${orig.line}:${orig.column}`;
return line.replace(bare[0], replacement);
} catch (error) {
console.debug && console.debug('Remap failed for bare frame:', line, error);
return line;
}
}
return line;
});
return out.join('\n');
}
/**
* Added with 9.0 runtimes.
* Allows the runtime to remap stack traces before displaying them via in-flight error screens.
*/
(global as any).__ns_remapStack = (rawStack: string) => {
// console.log('Remapping stack trace...');
try {
return remapStack(rawStack);
} catch (error) {
console.debug && console.debug('Remap failed, returning original:', error);
return rawStack; // fallback to original stack trace
}
};
/**
* End of source map remapping for stack traces
*/