Permalink
| (function(root, factory) { | |
| 'use strict'; | |
| // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, Rhino, and browsers. | |
| /* istanbul ignore next */ | |
| if (typeof define === 'function' && define.amd) { | |
| define('stacktrace', ['error-stack-parser', 'stack-generator', 'stacktrace-gps'], factory); | |
| } else if (typeof exports === 'object') { | |
| module.exports = factory(require('error-stack-parser'), require('stack-generator'), require('stacktrace-gps')); | |
| } else { | |
| root.StackTrace = factory(root.ErrorStackParser, root.StackGenerator, root.StackTraceGPS); | |
| } | |
| }(this, function StackTrace(ErrorStackParser, StackGenerator, StackTraceGPS) { | |
| var _options = { | |
| filter: function(stackframe) { | |
| // Filter out stackframes for this library by default | |
| return (stackframe.functionName || '').indexOf('StackTrace$$') === -1 && | |
| (stackframe.functionName || '').indexOf('ErrorStackParser$$') === -1 && | |
| (stackframe.functionName || '').indexOf('StackTraceGPS$$') === -1 && | |
| (stackframe.functionName || '').indexOf('StackGenerator$$') === -1; | |
| }, | |
| sourceCache: {} | |
| }; | |
| var _generateError = function StackTrace$$GenerateError() { | |
| try { | |
| // Error must be thrown to get stack in IE | |
| throw new Error(); | |
| } catch (err) { | |
| return err; | |
| } | |
| }; | |
| /** | |
| * Merge 2 given Objects. If a conflict occurs the second object wins. | |
| * Does not do deep merges. | |
| * | |
| * @param {Object} first base object | |
| * @param {Object} second overrides | |
| * @returns {Object} merged first and second | |
| * @private | |
| */ | |
| function _merge(first, second) { | |
| var target = {}; | |
| [first, second].forEach(function(obj) { | |
| for (var prop in obj) { | |
| if (obj.hasOwnProperty(prop)) { | |
| target[prop] = obj[prop]; | |
| } | |
| } | |
| return target; | |
| }); | |
| return target; | |
| } | |
| function _isShapedLikeParsableError(err) { | |
| return err.stack || err['opera#sourceloc']; | |
| } | |
| function _filtered(stackframes, filter) { | |
| if (typeof filter === 'function') { | |
| return stackframes.filter(filter); | |
| } | |
| return stackframes; | |
| } | |
| return { | |
| /** | |
| * Get a backtrace from invocation point. | |
| * | |
| * @param {Object} opts | |
| * @returns {Array} of StackFrame | |
| */ | |
| get: function StackTrace$$get(opts) { | |
| var err = _generateError(); | |
| return _isShapedLikeParsableError(err) ? this.fromError(err, opts) : this.generateArtificially(opts); | |
| }, | |
| /** | |
| * Get a backtrace from invocation point. | |
| * IMPORTANT: Does not handle source maps or guess function names! | |
| * | |
| * @param {Object} opts | |
| * @returns {Array} of StackFrame | |
| */ | |
| getSync: function StackTrace$$getSync(opts) { | |
| opts = _merge(_options, opts); | |
| var err = _generateError(); | |
| var stack = _isShapedLikeParsableError(err) ? ErrorStackParser.parse(err) : StackGenerator.backtrace(opts); | |
| return _filtered(stack, opts.filter); | |
| }, | |
| /** | |
| * Given an error object, parse it. | |
| * | |
| * @param {Error} error object | |
| * @param {Object} opts | |
| * @returns {Promise} for Array[StackFrame} | |
| */ | |
| fromError: function StackTrace$$fromError(error, opts) { | |
| opts = _merge(_options, opts); | |
| var gps = new StackTraceGPS(opts); | |
| return new Promise(function(resolve) { | |
| var stackframes = _filtered(ErrorStackParser.parse(error), opts.filter); | |
| resolve(Promise.all(stackframes.map(function(sf) { | |
| return new Promise(function(resolve) { | |
| function resolveOriginal() { | |
| resolve(sf); | |
| } | |
| gps.pinpoint(sf).then(resolve, resolveOriginal)['catch'](resolveOriginal); | |
| }); | |
| }))); | |
| }.bind(this)); | |
| }, | |
| /** | |
| * Use StackGenerator to generate a backtrace. | |
| * | |
| * @param {Object} opts | |
| * @returns {Promise} of Array[StackFrame] | |
| */ | |
| generateArtificially: function StackTrace$$generateArtificially(opts) { | |
| opts = _merge(_options, opts); | |
| var stackFrames = StackGenerator.backtrace(opts); | |
| if (typeof opts.filter === 'function') { | |
| stackFrames = stackFrames.filter(opts.filter); | |
| } | |
| return Promise.resolve(stackFrames); | |
| }, | |
| /** | |
| * Given a function, wrap it such that invocations trigger a callback that | |
| * is called with a stack trace. | |
| * | |
| * @param {Function} fn to be instrumented | |
| * @param {Function} callback function to call with a stack trace on invocation | |
| * @param {Function} errback optional function to call with error if unable to get stack trace. | |
| * @param {Object} thisArg optional context object (e.g. window) | |
| */ | |
| instrument: function StackTrace$$instrument(fn, callback, errback, thisArg) { | |
| if (typeof fn !== 'function') { | |
| throw new Error('Cannot instrument non-function object'); | |
| } else if (typeof fn.__stacktraceOriginalFn === 'function') { | |
| // Already instrumented, return given Function | |
| return fn; | |
| } | |
| var instrumented = function StackTrace$$instrumented() { | |
| try { | |
| this.get().then(callback, errback)['catch'](errback); | |
| return fn.apply(thisArg || this, arguments); | |
| } catch (e) { | |
| if (_isShapedLikeParsableError(e)) { | |
| this.fromError(e).then(callback, errback)['catch'](errback); | |
| } | |
| throw e; | |
| } | |
| }.bind(this); | |
| instrumented.__stacktraceOriginalFn = fn; | |
| return instrumented; | |
| }, | |
| /** | |
| * Given a function that has been instrumented, | |
| * revert the function to it's original (non-instrumented) state. | |
| * | |
| * @param {Function} fn to de-instrument | |
| */ | |
| deinstrument: function StackTrace$$deinstrument(fn) { | |
| if (typeof fn !== 'function') { | |
| throw new Error('Cannot de-instrument non-function object'); | |
| } else if (typeof fn.__stacktraceOriginalFn === 'function') { | |
| return fn.__stacktraceOriginalFn; | |
| } else { | |
| // Function not instrumented, return original | |
| return fn; | |
| } | |
| }, | |
| /** | |
| * Given an error message and Array of StackFrames, serialize and POST to given URL. | |
| * | |
| * @param {Array} stackframes | |
| * @param {String} url | |
| * @param {String} errorMsg | |
| * @param {Object} requestOptions | |
| */ | |
| report: function StackTrace$$report(stackframes, url, errorMsg, requestOptions) { | |
| return new Promise(function(resolve, reject) { | |
| var req = new XMLHttpRequest(); | |
| req.onerror = reject; | |
| req.onreadystatechange = function onreadystatechange() { | |
| if (req.readyState === 4) { | |
| if (req.status >= 200 && req.status < 400) { | |
| resolve(req.responseText); | |
| } else { | |
| reject(new Error('POST to ' + url + ' failed with status: ' + req.status)); | |
| } | |
| } | |
| }; | |
| req.open('post', url); | |
| // Set request headers | |
| req.setRequestHeader('Content-Type', 'application/json'); | |
| if (requestOptions && typeof requestOptions.headers === 'object') { | |
| var headers = requestOptions.headers; | |
| for (var header in headers) { | |
| if (headers.hasOwnProperty(header)) { | |
| req.setRequestHeader(header, headers[header]); | |
| } | |
| } | |
| } | |
| var reportPayload = {stack: stackframes}; | |
| if (errorMsg !== undefined && errorMsg !== null) { | |
| reportPayload.message = errorMsg; | |
| } | |
| req.send(JSON.stringify(reportPayload)); | |
| }); | |
| } | |
| }; | |
| })); |