UNPKG

weer

Version:

Web Extensions Error Reporter catches global errors, shows notifications and opens error reporter in one click

352 lines (272 loc) 9.91 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.Weer = global.Weer || {}, global.Weer.Utils = factory()); }(this, (function () { 'use strict'; function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var errio = createCommonjsModule(function (module, exports) { // Default options for all serializations. var defaultOptions = { recursive: true, // Recursively serialize and deserialize nested errors inherited: true, // Include inherited properties stack: false, // Include stack property private: false, // Include properties with leading or trailing underscores exclude: [], // Property names to exclude (low priority) include: [] // Property names to include (high priority) }; // Overwrite global default options. exports.setDefaults = function(options) { for (var key in options) defaultOptions[key] = options[key]; }; // Object containing registered error constructors and their options. var errors = {}; // Register an error constructor for serialization and deserialization with // option overrides. Name can be specified in options, otherwise it will be // taken from the prototype's name property (if it is not set to Error), the // constructor's name property, or the name property of an instance of the // constructor. exports.register = function(constructor, options) { options = options || {}; var prototypeName = constructor.prototype.name !== 'Error' ? constructor.prototype.name : null; var name = options.name || prototypeName || constructor.name || new constructor().name; errors[name] = { constructor: constructor, options: options }; }; // Register an array of error constructors all with the same option overrides. exports.registerAll = function(constructors, options) { constructors.forEach(function(constructor) { exports.register(constructor, options); }); }; // Shallow clone a plain object. function cloneObject(object) { var clone = {}; for (var key in object) { if (object.hasOwnProperty(key)) clone[key] = object[key]; } return clone; } // Register a plain object of constructor names mapped to constructors with // common option overrides. exports.registerObject = function(constructors, commonOptions) { for (var name in constructors) { if (!constructors.hasOwnProperty(name)) continue; var constructor = constructors[name]; var options = cloneObject(commonOptions); options.name = name; exports.register(constructor, options); } }; // Register the built-in error constructors. exports.registerAll([ Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError ]); // Serialize an error instance to a plain object with option overrides, applied // on top of the global defaults and the registered option overrides. If the // constructor of the error instance has not been registered yet, register it // with the provided options. exports.toObject = function(error, callOptions) { callOptions = callOptions || {}; if (!errors[error.name]) { // Make sure we register with the name of this instance. callOptions.name = error.name; exports.register(error.constructor, callOptions); } var errorOptions = errors[error.name].options; var options = {}; for (var key in defaultOptions) { if (callOptions.hasOwnProperty(key)) options[key] = callOptions[key]; else if (errorOptions.hasOwnProperty(key)) options[key] = errorOptions[key]; else options[key] = defaultOptions[key]; } // Always explicitly include essential error properties. var object = { name: error.name, message: error.message }; // Explicitly include stack since it is not always an enumerable property. if (options.stack) object.stack = error.stack; for (var prop in error) { // Skip exclusion checks if property is in include list. if (options.include.indexOf(prop) === -1) { if (typeof error[prop] === 'function') continue; if (options.exclude.indexOf(prop) !== -1) continue; if (!options.inherited) if (!error.hasOwnProperty(prop)) continue; if (!options.stack) if (prop === 'stack') continue; if (!options.private) if (prop[0] === '_' || prop[prop.length - 1] === '_') continue; } var value = error[prop]; // Recurse if nested object has name and message properties. if (typeof value === 'object' && value && value.name && value.message) { if (options.recursive) { object[prop] = exports.toObject(value, callOptions); } continue; } object[prop] = value; } return object; }; // Deserialize a plain object to an instance of a registered error constructor // with option overrides. If the specific constructor is not registered, // return a generic Error instance. If stack was not serialized, capture a new // stack trace. exports.fromObject = function(object, callOptions) { callOptions = callOptions || {}; var registration = errors[object.name]; if (!registration) registration = errors.Error; var constructor = registration.constructor; var errorOptions = registration.options; var options = {}; for (var key in defaultOptions) { if (callOptions.hasOwnProperty(key)) options[key] = callOptions[key]; else if (errorOptions.hasOwnProperty(key)) options[key] = errorOptions[key]; else options[key] = defaultOptions[key]; } // Instantiate the error without actually calling the constructor. var error = Object.create(constructor.prototype); for (var prop in object) { // Recurse if nested object has name and message properties. if (options.recursive && typeof object[prop] === 'object') { var nested = object[prop]; if (nested && nested.name && nested.message) { error[prop] = exports.fromObject(nested, callOptions); continue; } } error[prop] = object[prop]; } // Capture a new stack trace such that the first trace line is the caller of // fromObject. if (!error.stack && Error.captureStackTrace) { Error.captureStackTrace(error, exports.fromObject); } return error; }; // Serialize an error instance to a JSON string with option overrides. exports.stringify = function(error, callOptions) { return JSON.stringify(exports.toObject(error, callOptions)); }; // Deserialize a JSON string to an instance of a registered error constructor. exports.parse = function(string, callOptions) { return exports.fromObject(JSON.parse(string), callOptions); }; }); var errio_1 = errio.setDefaults; var errio_2 = errio.register; var errio_3 = errio.registerAll; var errio_4 = errio.registerObject; var errio_5 = errio.toObject; var errio_6 = errio.fromObject; var errio_7 = errio.stringify; var errio_8 = errio.parse; /* # Purpose 1. `timeouted` wrapper that makes error catching possible. 2. Convert error-first callbacks for use by chrome API: `chromified`. 3. Add utils for safer coding: `mandatory`, `throwIfError`. */ const Utils = { mandatory() { throw new TypeError('Missing required argument. Be explicit if you swallow errors.'); }, throwIfError(err) { if (err) { throw err; } }, checkChromeError() { // Chrome API calls your cb in a context different from the point of API // method invokation. const err = chrome.runtime.lastError || chrome.extension.lastError; if (!err) { return; } /* Example of lastError: `chrome.runtime.openOptionsPage(() => console.log(chrome.runtime.lastError))` {message: "Could not create an options page."} */ return new Error(err.message); // Add stack. }, timeouted(cb = Utils.mandatory) { // setTimeout fixes error context, see https://crbug.com/357568 return (...args) => { setTimeout(() => cb(...args), 0); }; }, chromified(cb = Utils.mandatory()) { // Take error first callback and convert it to chrome API callback. return function wrapper(...args) { const err = Utils.checkChromeError(); Utils.timeouted(cb)(err, ...args); }; }, getOrDie(cb = Utils.mandatory()) { return Utils.chromified((err, ...args) => { if (err) { throw err; } cb(...args); }); }, assert(value, message) { if (!value) { throw new Error(message || `Assertion failed, value: ${value}`); } }, errorToPlainObject(error) { return errio.toObject(error, { stack: true, private: true }); }, errorEventToPlainObject(errorEvent) { const plainObj = [ 'message', 'filename', 'lineno', 'colno', 'type', 'path', ].reduce((acc, prop) => { acc[prop] = errorEvent[prop]; return acc; }, {}); if (plainObj.path) { const pathStr = plainObj.path.map((o) => { let res = ''; if (o.tagName) { res += `<${o.tagName.toLowerCase()}`; if (o.attributes) { res += Array.from(o.attributes).map((atr) => ` ${atr.name}="${atr.value}"`).join(''); } res += '>'; } if (!res) { res += `${o}`; } return res; }).join(', '); plainObj.path = `[${pathStr}]`; } if (errorEvent.error && typeof errorEvent === 'object') { plainObj.error = this.errorToPlainObject(errorEvent.error); } else { plainObj.error = errorEvent.error; } return plainObj; }, }; return Utils; })));