source: imaps-frontend/node_modules/stacktrace-gps/stacktrace-gps.js@ d565449

main
Last change on this file since d565449 was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 14.6 KB
Line 
1(function(root, factory) {
2 'use strict';
3 // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, Rhino, and browsers.
4
5 /* istanbul ignore next */
6 if (typeof define === 'function' && define.amd) {
7 define('stacktrace-gps', ['source-map', 'stackframe'], factory);
8 } else if (typeof exports === 'object') {
9 module.exports = factory(require('source-map/lib/source-map-consumer'), require('stackframe'));
10 } else {
11 root.StackTraceGPS = factory(root.SourceMap || root.sourceMap, root.StackFrame);
12 }
13}(this, function(SourceMap, StackFrame) {
14 'use strict';
15
16 /**
17 * Make a X-Domain request to url and callback.
18 *
19 * @param {String} url
20 * @returns {Promise} with response text if fulfilled
21 */
22 function _xdr(url) {
23 return new Promise(function(resolve, reject) {
24 var req = new XMLHttpRequest();
25 req.open('get', url);
26 req.onerror = reject;
27 req.onreadystatechange = function onreadystatechange() {
28 if (req.readyState === 4) {
29 if ((req.status >= 200 && req.status < 300) ||
30 (url.substr(0, 7) === 'file://' && req.responseText)) {
31 resolve(req.responseText);
32 } else {
33 reject(new Error('HTTP status: ' + req.status + ' retrieving ' + url));
34 }
35 }
36 };
37 req.send();
38 });
39
40 }
41
42 /**
43 * Convert a Base64-encoded string into its original representation.
44 * Used for inline sourcemaps.
45 *
46 * @param {String} b64str Base-64 encoded string
47 * @returns {String} original representation of the base64-encoded string.
48 */
49 function _atob(b64str) {
50 if (typeof window !== 'undefined' && window.atob) {
51 return window.atob(b64str);
52 } else {
53 throw new Error('You must supply a polyfill for window.atob in this environment');
54 }
55 }
56
57 function _parseJson(string) {
58 if (typeof JSON !== 'undefined' && JSON.parse) {
59 return JSON.parse(string);
60 } else {
61 throw new Error('You must supply a polyfill for JSON.parse in this environment');
62 }
63 }
64
65 function _findFunctionName(source, lineNumber/*, columnNumber*/) {
66 var syntaxes = [
67 // {name} = function ({args}) TODO args capture
68 /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*function\b/,
69 // function {name}({args}) m[1]=name m[2]=args
70 /function\s+([^('"`]*?)\s*\(([^)]*)\)/,
71 // {name} = eval()
72 /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*(?:eval|new Function)\b/,
73 // fn_name() {
74 /\b(?!(?:if|for|switch|while|with|catch)\b)(?:(?:static)\s+)?(\S+)\s*\(.*?\)\s*\{/,
75 // {name} = () => {
76 /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*\(.*?\)\s*=>/
77 ];
78 var lines = source.split('\n');
79
80 // Walk backwards in the source lines until we find the line which matches one of the patterns above
81 var code = '';
82 var maxLines = Math.min(lineNumber, 20);
83 for (var i = 0; i < maxLines; ++i) {
84 // lineNo is 1-based, source[] is 0-based
85 var line = lines[lineNumber - i - 1];
86 var commentPos = line.indexOf('//');
87 if (commentPos >= 0) {
88 line = line.substr(0, commentPos);
89 }
90
91 if (line) {
92 code = line + code;
93 var len = syntaxes.length;
94 for (var index = 0; index < len; index++) {
95 var m = syntaxes[index].exec(code);
96 if (m && m[1]) {
97 return m[1];
98 }
99 }
100 }
101 }
102 return undefined;
103 }
104
105 function _ensureSupportedEnvironment() {
106 if (typeof Object.defineProperty !== 'function' || typeof Object.create !== 'function') {
107 throw new Error('Unable to consume source maps in older browsers');
108 }
109 }
110
111 function _ensureStackFrameIsLegit(stackframe) {
112 if (typeof stackframe !== 'object') {
113 throw new TypeError('Given StackFrame is not an object');
114 } else if (typeof stackframe.fileName !== 'string') {
115 throw new TypeError('Given file name is not a String');
116 } else if (typeof stackframe.lineNumber !== 'number' ||
117 stackframe.lineNumber % 1 !== 0 ||
118 stackframe.lineNumber < 1) {
119 throw new TypeError('Given line number must be a positive integer');
120 } else if (typeof stackframe.columnNumber !== 'number' ||
121 stackframe.columnNumber % 1 !== 0 ||
122 stackframe.columnNumber < 0) {
123 throw new TypeError('Given column number must be a non-negative integer');
124 }
125 return true;
126 }
127
128 function _findSourceMappingURL(source) {
129 var sourceMappingUrlRegExp = /\/\/[#@] ?sourceMappingURL=([^\s'"]+)\s*$/mg;
130 var lastSourceMappingUrl;
131 var matchSourceMappingUrl;
132 // eslint-disable-next-line no-cond-assign
133 while (matchSourceMappingUrl = sourceMappingUrlRegExp.exec(source)) {
134 lastSourceMappingUrl = matchSourceMappingUrl[1];
135 }
136 if (lastSourceMappingUrl) {
137 return lastSourceMappingUrl;
138 } else {
139 throw new Error('sourceMappingURL not found');
140 }
141 }
142
143 function _extractLocationInfoFromSourceMapSource(stackframe, sourceMapConsumer, sourceCache) {
144 return new Promise(function(resolve, reject) {
145 var loc = sourceMapConsumer.originalPositionFor({
146 line: stackframe.lineNumber,
147 column: stackframe.columnNumber
148 });
149
150 if (loc.source) {
151 // cache mapped sources
152 var mappedSource = sourceMapConsumer.sourceContentFor(loc.source);
153 if (mappedSource) {
154 sourceCache[loc.source] = mappedSource;
155 }
156
157 resolve(
158 // given stackframe and source location, update stackframe
159 new StackFrame({
160 functionName: loc.name || stackframe.functionName,
161 args: stackframe.args,
162 fileName: loc.source,
163 lineNumber: loc.line,
164 columnNumber: loc.column
165 }));
166 } else {
167 reject(new Error('Could not get original source for given stackframe and source map'));
168 }
169 });
170 }
171
172 /**
173 * @constructor
174 * @param {Object} opts
175 * opts.sourceCache = {url: "Source String"} => preload source cache
176 * opts.sourceMapConsumerCache = {/path/file.js.map: SourceMapConsumer}
177 * opts.offline = True to prevent network requests.
178 * Best effort without sources or source maps.
179 * opts.ajax = Promise returning function to make X-Domain requests
180 */
181 return function StackTraceGPS(opts) {
182 if (!(this instanceof StackTraceGPS)) {
183 return new StackTraceGPS(opts);
184 }
185 opts = opts || {};
186
187 this.sourceCache = opts.sourceCache || {};
188 this.sourceMapConsumerCache = opts.sourceMapConsumerCache || {};
189
190 this.ajax = opts.ajax || _xdr;
191
192 this._atob = opts.atob || _atob;
193
194 this._get = function _get(location) {
195 return new Promise(function(resolve, reject) {
196 var isDataUrl = location.substr(0, 5) === 'data:';
197 if (this.sourceCache[location]) {
198 resolve(this.sourceCache[location]);
199 } else if (opts.offline && !isDataUrl) {
200 reject(new Error('Cannot make network requests in offline mode'));
201 } else {
202 if (isDataUrl) {
203 // data URLs can have parameters.
204 // see http://tools.ietf.org/html/rfc2397
205 var supportedEncodingRegexp =
206 /^data:application\/json;([\w=:"-]+;)*base64,/;
207 var match = location.match(supportedEncodingRegexp);
208 if (match) {
209 var sourceMapStart = match[0].length;
210 var encodedSource = location.substr(sourceMapStart);
211 var source = this._atob(encodedSource);
212 this.sourceCache[location] = source;
213 resolve(source);
214 } else {
215 reject(new Error('The encoding of the inline sourcemap is not supported'));
216 }
217 } else {
218 var xhrPromise = this.ajax(location, {method: 'get'});
219 // Cache the Promise to prevent duplicate in-flight requests
220 this.sourceCache[location] = xhrPromise;
221 xhrPromise.then(resolve, reject);
222 }
223 }
224 }.bind(this));
225 };
226
227 /**
228 * Creating SourceMapConsumers is expensive, so this wraps the creation of a
229 * SourceMapConsumer in a per-instance cache.
230 *
231 * @param {String} sourceMappingURL = URL to fetch source map from
232 * @param {String} defaultSourceRoot = Default source root for source map if undefined
233 * @returns {Promise} that resolves a SourceMapConsumer
234 */
235 this._getSourceMapConsumer = function _getSourceMapConsumer(sourceMappingURL, defaultSourceRoot) {
236 return new Promise(function(resolve) {
237 if (this.sourceMapConsumerCache[sourceMappingURL]) {
238 resolve(this.sourceMapConsumerCache[sourceMappingURL]);
239 } else {
240 var sourceMapConsumerPromise = new Promise(function(resolve, reject) {
241 return this._get(sourceMappingURL).then(function(sourceMapSource) {
242 if (typeof sourceMapSource === 'string') {
243 sourceMapSource = _parseJson(sourceMapSource.replace(/^\)\]\}'/, ''));
244 }
245 if (typeof sourceMapSource.sourceRoot === 'undefined') {
246 sourceMapSource.sourceRoot = defaultSourceRoot;
247 }
248
249 resolve(new SourceMap.SourceMapConsumer(sourceMapSource));
250 }).catch(reject);
251 }.bind(this));
252 this.sourceMapConsumerCache[sourceMappingURL] = sourceMapConsumerPromise;
253 resolve(sourceMapConsumerPromise);
254 }
255 }.bind(this));
256 };
257
258 /**
259 * Given a StackFrame, enhance function name and use source maps for a
260 * better StackFrame.
261 *
262 * @param {StackFrame} stackframe object
263 * @returns {Promise} that resolves with with source-mapped StackFrame
264 */
265 this.pinpoint = function StackTraceGPS$$pinpoint(stackframe) {
266 return new Promise(function(resolve, reject) {
267 this.getMappedLocation(stackframe).then(function(mappedStackFrame) {
268 function resolveMappedStackFrame() {
269 resolve(mappedStackFrame);
270 }
271
272 this.findFunctionName(mappedStackFrame)
273 .then(resolve, resolveMappedStackFrame)
274 // eslint-disable-next-line no-unexpected-multiline
275 ['catch'](resolveMappedStackFrame);
276 }.bind(this), reject);
277 }.bind(this));
278 };
279
280 /**
281 * Given a StackFrame, guess function name from location information.
282 *
283 * @param {StackFrame} stackframe
284 * @returns {Promise} that resolves with enhanced StackFrame.
285 */
286 this.findFunctionName = function StackTraceGPS$$findFunctionName(stackframe) {
287 return new Promise(function(resolve, reject) {
288 _ensureStackFrameIsLegit(stackframe);
289 this._get(stackframe.fileName).then(function getSourceCallback(source) {
290 var lineNumber = stackframe.lineNumber;
291 var columnNumber = stackframe.columnNumber;
292 var guessedFunctionName = _findFunctionName(source, lineNumber, columnNumber);
293 // Only replace functionName if we found something
294 if (guessedFunctionName) {
295 resolve(new StackFrame({
296 functionName: guessedFunctionName,
297 args: stackframe.args,
298 fileName: stackframe.fileName,
299 lineNumber: lineNumber,
300 columnNumber: columnNumber
301 }));
302 } else {
303 resolve(stackframe);
304 }
305 }, reject)['catch'](reject);
306 }.bind(this));
307 };
308
309 /**
310 * Given a StackFrame, seek source-mapped location and return new enhanced StackFrame.
311 *
312 * @param {StackFrame} stackframe
313 * @returns {Promise} that resolves with enhanced StackFrame.
314 */
315 this.getMappedLocation = function StackTraceGPS$$getMappedLocation(stackframe) {
316 return new Promise(function(resolve, reject) {
317 _ensureSupportedEnvironment();
318 _ensureStackFrameIsLegit(stackframe);
319
320 var sourceCache = this.sourceCache;
321 var fileName = stackframe.fileName;
322 this._get(fileName).then(function(source) {
323 var sourceMappingURL = _findSourceMappingURL(source);
324 var isDataUrl = sourceMappingURL.substr(0, 5) === 'data:';
325 var defaultSourceRoot = fileName.substring(0, fileName.lastIndexOf('/') + 1);
326
327 if (sourceMappingURL[0] !== '/' && !isDataUrl && !(/^https?:\/\/|^\/\//i).test(sourceMappingURL)) {
328 sourceMappingURL = defaultSourceRoot + sourceMappingURL;
329 }
330
331 return this._getSourceMapConsumer(sourceMappingURL, defaultSourceRoot)
332 .then(function(sourceMapConsumer) {
333 return _extractLocationInfoFromSourceMapSource(stackframe, sourceMapConsumer, sourceCache)
334 .then(resolve)['catch'](function() {
335 resolve(stackframe);
336 });
337 });
338 }.bind(this), reject)['catch'](reject);
339 }.bind(this));
340 };
341 };
342}));
Note: See TracBrowser for help on using the repository browser.