UNPKG

doushio

Version:
343 lines (290 loc) 10.7 kB
// // **Bugsnag.js** is the official JavaScript notifier for // [Bugsnag](https://bugsnag.com). // // Bugsnag gives you instant notification of errors and // exceptions in your website's JavaScript code. // // Bugsnag.js is incredibly small, and has no external dependencies (not even // jQuery!) so you can safely use it on any website. // // The `Bugsnag` object is the only globally exported variable window.Bugsnag = (function (window, document, navigator) { var self = { apiKey: '1adededf5190d8c0f5cb6b407f5c2467', releaseStage: config.DEBUG ? 'development' : 'production', notifyReleaseStages: ['production'] }; // // ### Manual error notification (public methods) // // #### Bugsnag.notifyException // // Notify Bugsnag about a given `exception`, typically that you've caught // with a `try/catch` statement or that you've generated yourself. // // Since most JavaScript exceptions use the `Error` class, we also allow // you to provide a custom error name when calling `notifyException`. self.notifyException = function (exception, name, metaData) { if (typeof name !== "string") { metaData = name; } sendToBugsnag({ name: name || exception.name, message: exception.message || exception.description, stacktrace: stacktraceFromException(exception) || generateStacktrace(), file: exception.fileName || exception.sourceURL, lineNumber: exception.lineNumber || exception.line }, metaData); }; // #### Bugsnag.notify // // Notify Bugsnag about an error by passing in a `name` and `message`, // without requiring an exception. self.notify = function (name, message, metaData) { sendToBugsnag({ name: name, message: message, stacktrace: generateStacktrace() }, metaData); }; // // ### Automatic error notification // // Keep a reference to any existing `window.onerror` handler self._onerror = window.onerror; // Attach to `window.onerror` events and notify Bugsnag when they happen. // These are mostly js compile/parse errors, but on some browsers all // "uncaught" exceptions will fire this event. window.onerror = function (message, url, lineNo) { sendToBugsnag({ name: "window.onerror", message: message, file: url, lineNumber: lineNo }); // Fire the existing `window.onerror` handler, if one exists if (self._onerror) { self._onerror(message, url, lineNo); } }; // // ### Helpers & Setup // // Compile regular expressions upfront. var FUNCTION_REGEX = /function\s*([\w\-$]+)?\s*\(/i; // Set up default notifier settings. var DEFAULT_BASE_ENDPOINT = "https://notify.bugsnag.com/"; var DEFAULT_NOTIFIER_ENDPOINT = DEFAULT_BASE_ENDPOINT + "js"; var DEFAULT_METRICS_ENDPOINT = DEFAULT_BASE_ENDPOINT + "metrics"; var NOTIFIER_VERSION = "1.0.6"; var DEFAULT_RELEASE_STAGE = "production"; var DEFAULT_NOTIFY_RELEASE_STAGES = [DEFAULT_RELEASE_STAGE]; // Keep a reference to the currently executing script in the DOM. // We'll use this later to extract settings from attributes. var scripts = document.getElementsByTagName("script"); var thisScript = scripts[scripts.length - 1]; // Simple logging function that wraps `console.log` if available. // This is useful for warning about configuration issues // eg. forgetting to set an API key. function log(msg) { var console = window.console; if (console !== undefined && console.log !== undefined) { console.log("[Bugsnag] " + msg); } } // Deeply serialize an object into a query string. We use the PHP-style // nested object syntax, `nested[keys]=val`, to support heirachical // objects. Similar to jQuery's `$.param` method. function serialize(obj, prefix) { var str = []; for (var p in obj) { if (obj.hasOwnProperty(p) && p != null && obj[p] != null) { var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p]; str.push(typeof v === "object" ? serialize(v, k) : encodeURIComponent(k) + "=" + encodeURIComponent(v)); } } return str.join("&"); } // Deep-merge the `source` object into the `target` object and return // the `target`. Properties in source that will overwrite those in target. // Similar to jQuery's `$.extend` method. function merge(target, source) { if (source == null) { return target; } target = target || {}; for (var key in source) { if (source.hasOwnProperty(key)) { try { if (source[key].constructor === Object) { target[key] = merge(target[key], source[key]); } else { target[key] = source[key]; } } catch (e) { target[key] = source[key]; } } } return target; } // Make a HTTP request with given `url` and `params` object. // For maximum browser compatibility and cross-domain support, requests are // made by creating a temporary JavaScript `Image` object. function request(url, params) { if (!self.testRequest) { var img = new Image(); img.src = url + "?" + serialize(params) + "&ct=img&cb=" + new Date().getTime(); } else { self.testRequest(url, params); } } // Extract all `data-*` attributes from a DOM element and return them as an // object. This is used to allow Bugsnag settings to be set via attributes // on the `script` tag, eg. `<script data-apikey="xyz">`. // Similar to jQuery's `$(el).data()` method. function getData(node) { var dataAttrs = {}; var dataRegex = /^data\-([\w\-]+)$/; var attrs = node.attributes; for (var i = 0; i < attrs.length; i++) { var attr = attrs[i]; if (dataRegex.test(attr.nodeName)) { var key = attr.nodeName.match(dataRegex)[1]; dataAttrs[key] = attr.nodeValue; } } return dataAttrs; } // Send an error to Bugsnag. function sendToBugsnag(details, metaData) { // Validate the configured API key. var apiKey = self.apiKey; // Check if we should notify for this release stage. var releaseStage = self.releaseStage || DEFAULT_RELEASE_STAGE; var notifyReleaseStages = self.notifyReleaseStages || DEFAULT_NOTIFY_RELEASE_STAGES; var shouldNotify = false; for (var i = 0; i < notifyReleaseStages.length; i++) { if (releaseStage === notifyReleaseStages[i]) { shouldNotify = true; break; } } if (!shouldNotify) { return; } // Merge the local and global `metaData`. var mergedMetaData = merge(self.metaData, metaData); // Make the request: // // - Work out which endpoint to send to. // - Combine error information with other data such as // user-agent and locale, `metaData` and settings. // - Make the HTTP request. var location = window.location; request(self.endpoint || DEFAULT_NOTIFIER_ENDPOINT, { notifierVersion: NOTIFIER_VERSION, apiKey: apiKey, projectRoot: self.projectRoot || location.protocol + "//" + location.host, context: self.context || location.pathname, metaData: mergedMetaData, releaseStage: releaseStage, url: window.location.href, userAgent: navigator.userAgent, language: navigator.language || navigator.userLanguage, name: details.name, message: details.message, stacktrace: details.stacktrace, file: details.file, lineNumber: details.lineNumber }); } // Generate a browser stacktrace (or approximation) from the current stack. // This is used to add a stacktrace to `Bugsnag.notify` calls, and to add a // stacktrace approximation where we can't get one from an exception. function generateStacktrace() { var stacktrace; var MAX_FAKE_STACK_SIZE = 10; var ANONYMOUS_FUNCTION_PLACEHOLDER = "[anonymous]"; // Try to generate a real stacktrace (most browsers, except IE9 and below). try { throw new Error(""); } catch (exception) { stacktrace = stacktraceFromException(exception); } // Otherwise, build a fake stacktrace from the list of method names by // looping through the list of functions that called this one (and skip // whoever called us). if (!stacktrace) { var functionStack = []; var curr = arguments.callee.caller.caller; while (curr && functionStack.length < MAX_FAKE_STACK_SIZE) { var fn = FUNCTION_REGEX.test(curr.toString()) ? RegExp.$1 || ANONYMOUS_FUNCTION_PLACEHOLDER : ANONYMOUS_FUNCTION_PLACEHOLDER; functionStack.push(fn); curr = curr.caller; } stacktrace = functionStack.join("\n"); } return stacktrace; } // Get the stacktrace string from an exception function stacktraceFromException(exception) { return exception.stack || exception.backtrace || exception.stacktrace; } // // ### Metrics tracking (DAU/MAU) // // Track a page-view for MAU/DAU metrics. function trackMetrics() { var shouldTrack = self.metrics; var apiKey = self.apiKey; if ((shouldTrack !== true && shouldTrack !== "true")) { return; } // Fetch or generate a userId var cookieName = "bugsnag_" + apiKey; var userId = getCookie(cookieName); if (userId == null) { userId = generateUUID(); setCookie(cookieName, userId, 1000, true); } // Make the HTTP request. request(self.metricsEndpoint || DEFAULT_METRICS_ENDPOINT, { userId: userId, apiKey: apiKey }); } // Generate a 4-character random hex string. function s4() { return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); } // Generate a version-4 UUID. function generateUUID() { return s4() + s4() + "-" + s4() + "-" + s4() + "-" + s4() + "-" + s4() + s4() + s4(); } // Set a cookie value. function setCookie(name, value, days, crossSubdomain) { var cdomain = ""; var expires = ""; if (days) { var date = new Date(); date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); expires = "; expires=" + date.toGMTString(); } if (crossSubdomain) { var matches = window.location.hostname.match(/[a-z0-9][a-z0-9\-]+\.[a-z\.]{2,6}$/i); var domain = matches ? matches[0] : ""; cdomain = (domain ? "; domain=." + domain : ""); } document.cookie = name + "=" + encodeURIComponent(value) + expires + "; path=/" + cdomain; } // Get a cookie value. function getCookie(name) { var cookie = document.cookie.match(name + "=([^$;]+)"); return cookie ? decodeURIComponent(cookie[1]) : null; } // Make a metrics request to Bugsnag if enabled. trackMetrics(); return self; }(window, document, navigator));