var Client = require('../rollbar'); var _ = require('../utility'); var API = require('../api'); var logger = require('./logger'); var globals = require('./globalSetup'); var Transport = require('./transport'); var urllib = require('./url'); var transforms = require('./transforms'); var sharedTransforms = require('../transforms'); var predicates = require('./predicates'); var sharedPredicates = require('../predicates'); var errorParser = require('../errorParser'); function Rollbar(options, client) { this.options = _.handleOptions(defaultOptions, options, null, logger); this.options._configuredOptions = options; var Telemeter = this.components.telemeter; var Instrumenter = this.components.instrumenter; var polyfillJSON = this.components.polyfillJSON; this.wrapGlobals = this.components.wrapGlobals; this.scrub = this.components.scrub; var truncation = this.components.truncation; var transport = new Transport(truncation); var api = new API(this.options, transport, urllib, truncation); if (Telemeter) { this.telemeter = new Telemeter(this.options); } this.client = client || new Client(this.options, api, logger, this.telemeter, 'browser'); var gWindow = _gWindow(); var gDocument = typeof document != 'undefined' && document; this.isChrome = gWindow.chrome && gWindow.chrome.runtime; // check .runtime to avoid Edge browsers this.anonymousErrorsPending = 0; addTransformsToNotifier(this.client.notifier, this, gWindow); addPredicatesToQueue(this.client.queue); this.setupUnhandledCapture(); if (Instrumenter) { this.instrumenter = new Instrumenter( this.options, this.client.telemeter, this, gWindow, gDocument, ); this.instrumenter.instrument(); } _.setupJSON(polyfillJSON); // Used with rollbar-react for rollbar-react-native compatibility. this.rollbar = this; } var _instance = null; Rollbar.init = function (options, client) { if (_instance) { return _instance.global(options).configure(options); } _instance = new Rollbar(options, client); return _instance; }; Rollbar.prototype.components = {}; Rollbar.setComponents = function (components) { Rollbar.prototype.components = components; }; function handleUninitialized(maybeCallback) { var message = 'Rollbar is not initialized'; logger.error(message); if (maybeCallback) { maybeCallback(new Error(message)); } } Rollbar.prototype.global = function (options) { this.client.global(options); return this; }; Rollbar.global = function (options) { if (_instance) { return _instance.global(options); } else { handleUninitialized(); } }; Rollbar.prototype.configure = function (options, payloadData) { var oldOptions = this.options; var payload = {}; if (payloadData) { payload = { payload: payloadData }; } this.options = _.handleOptions(oldOptions, options, payload, logger); this.options._configuredOptions = _.handleOptions( oldOptions._configuredOptions, options, payload, ); this.client.configure(this.options, payloadData); this.instrumenter && this.instrumenter.configure(this.options); this.setupUnhandledCapture(); return this; }; Rollbar.configure = function (options, payloadData) { if (_instance) { return _instance.configure(options, payloadData); } else { handleUninitialized(); } }; Rollbar.prototype.lastError = function () { return this.client.lastError; }; Rollbar.lastError = function () { if (_instance) { return _instance.lastError(); } else { handleUninitialized(); } }; Rollbar.prototype.log = function () { var item = this._createItem(arguments); var uuid = item.uuid; this.client.log(item); return { uuid: uuid }; }; Rollbar.log = function () { if (_instance) { return _instance.log.apply(_instance, arguments); } else { var maybeCallback = _getFirstFunction(arguments); handleUninitialized(maybeCallback); } }; Rollbar.prototype.debug = function () { var item = this._createItem(arguments); var uuid = item.uuid; this.client.debug(item); return { uuid: uuid }; }; Rollbar.debug = function () { if (_instance) { return _instance.debug.apply(_instance, arguments); } else { var maybeCallback = _getFirstFunction(arguments); handleUninitialized(maybeCallback); } }; Rollbar.prototype.info = function () { var item = this._createItem(arguments); var uuid = item.uuid; this.client.info(item); return { uuid: uuid }; }; Rollbar.info = function () { if (_instance) { return _instance.info.apply(_instance, arguments); } else { var maybeCallback = _getFirstFunction(arguments); handleUninitialized(maybeCallback); } }; Rollbar.prototype.warn = function () { var item = this._createItem(arguments); var uuid = item.uuid; this.client.warn(item); return { uuid: uuid }; }; Rollbar.warn = function () { if (_instance) { return _instance.warn.apply(_instance, arguments); } else { var maybeCallback = _getFirstFunction(arguments); handleUninitialized(maybeCallback); } }; Rollbar.prototype.warning = function () { var item = this._createItem(arguments); var uuid = item.uuid; this.client.warning(item); return { uuid: uuid }; }; Rollbar.warning = function () { if (_instance) { return _instance.warning.apply(_instance, arguments); } else { var maybeCallback = _getFirstFunction(arguments); handleUninitialized(maybeCallback); } }; Rollbar.prototype.error = function () { var item = this._createItem(arguments); var uuid = item.uuid; this.client.error(item); return { uuid: uuid }; }; Rollbar.error = function () { if (_instance) { return _instance.error.apply(_instance, arguments); } else { var maybeCallback = _getFirstFunction(arguments); handleUninitialized(maybeCallback); } }; Rollbar.prototype.critical = function () { var item = this._createItem(arguments); var uuid = item.uuid; this.client.critical(item); return { uuid: uuid }; }; Rollbar.critical = function () { if (_instance) { return _instance.critical.apply(_instance, arguments); } else { var maybeCallback = _getFirstFunction(arguments); handleUninitialized(maybeCallback); } }; Rollbar.prototype.buildJsonPayload = function (item) { return this.client.buildJsonPayload(item); }; Rollbar.buildJsonPayload = function () { if (_instance) { return _instance.buildJsonPayload.apply(_instance, arguments); } else { handleUninitialized(); } }; Rollbar.prototype.sendJsonPayload = function (jsonPayload) { return this.client.sendJsonPayload(jsonPayload); }; Rollbar.sendJsonPayload = function () { if (_instance) { return _instance.sendJsonPayload.apply(_instance, arguments); } else { handleUninitialized(); } }; Rollbar.prototype.setupUnhandledCapture = function () { var gWindow = _gWindow(); if (!this.unhandledExceptionsInitialized) { if (this.options.captureUncaught || this.options.handleUncaughtExceptions) { globals.captureUncaughtExceptions(gWindow, this); if (this.wrapGlobals && this.options.wrapGlobalEventHandlers) { this.wrapGlobals(gWindow, this); } this.unhandledExceptionsInitialized = true; } } if (!this.unhandledRejectionsInitialized) { if ( this.options.captureUnhandledRejections || this.options.handleUnhandledRejections ) { globals.captureUnhandledRejections(gWindow, this); this.unhandledRejectionsInitialized = true; } } }; Rollbar.prototype.handleUncaughtException = function ( message, url, lineno, colno, error, context, ) { if (!this.options.captureUncaught && !this.options.handleUncaughtExceptions) { return; } // Chrome will always send 5+ arguments and error will be valid or null, not undefined. // If error is undefined, we have a different caller. // Chrome also sends errors from web workers with null error, but does not invoke // prepareStackTrace() for these. Test for empty url to skip them. if ( this.options.inspectAnonymousErrors && this.isChrome && error === null && url === '' ) { return 'anonymous'; } var item; var stackInfo = _.makeUnhandledStackInfo( message, url, lineno, colno, error, 'onerror', 'uncaught exception', errorParser, ); if (_.isError(error)) { item = this._createItem([message, error, context]); item._unhandledStackInfo = stackInfo; } else if (_.isError(url)) { item = this._createItem([message, url, context]); item._unhandledStackInfo = stackInfo; } else { item = this._createItem([message, context]); item.stackInfo = stackInfo; } item.level = this.options.uncaughtErrorLevel; item._isUncaught = true; this.client.log(item); }; /** * Chrome only. Other browsers will ignore. * * Use Error.prepareStackTrace to extract information about errors that * do not have a valid error object in onerror(). * * In tested version of Chrome, onerror is called first but has no way * to communicate with prepareStackTrace. Use a counter to let this * handler know which errors to send to Rollbar. * * In config options, set inspectAnonymousErrors to enable. */ Rollbar.prototype.handleAnonymousErrors = function () { if (!this.options.inspectAnonymousErrors || !this.isChrome) { return; } var r = this; function prepareStackTrace(error, _stack) { // eslint-disable-line no-unused-vars if (r.options.inspectAnonymousErrors) { if (r.anonymousErrorsPending) { // This is the only known way to detect that onerror saw an anonymous error. // It depends on onerror reliably being called before Error.prepareStackTrace, // which so far holds true on tested versions of Chrome. If versions of Chrome // are tested that behave differently, this logic will need to be updated // accordingly. r.anonymousErrorsPending -= 1; if (!error) { // Not likely to get here, but calling handleUncaughtException from here // without an error object would throw off the anonymousErrorsPending counter, // so return now. return; } // Allow this to be tracked later. error._isAnonymous = true; // url, lineno, colno shouldn't be needed for these errors. // If that changes, update this accordingly, using the unused // _stack param as needed (rather than parse error.toString()). r.handleUncaughtException(error.message, null, null, null, error); } } // Workaround to ensure stack is preserved for normal errors. return error.stack; } // https://v8.dev/docs/stack-trace-api try { Error.prepareStackTrace = prepareStackTrace; } catch (e) { this.options.inspectAnonymousErrors = false; this.error('anonymous error handler failed', e); } }; Rollbar.prototype.handleUnhandledRejection = function (reason, promise) { if ( !this.options.captureUnhandledRejections && !this.options.handleUnhandledRejections ) { return; } var message = 'unhandled rejection was null or undefined!'; if (reason) { if (reason.message) { message = reason.message; } else { var reasonResult = _.stringify(reason); if (reasonResult.value) { message = reasonResult.value; } } } var context = (reason && reason._rollbarContext) || (promise && promise._rollbarContext); var item; if (_.isError(reason)) { item = this._createItem([message, reason, context]); } else { item = this._createItem([message, reason, context]); item.stackInfo = _.makeUnhandledStackInfo( message, '', 0, 0, null, 'unhandledrejection', '', errorParser, ); } item.level = this.options.uncaughtErrorLevel; item._isUncaught = true; item._originalArgs = item._originalArgs || []; item._originalArgs.push(promise); this.client.log(item); }; Rollbar.prototype.wrap = function (f, context, _before) { try { var ctxFn; if (_.isFunction(context)) { ctxFn = context; } else { ctxFn = function () { return context || {}; }; } if (!_.isFunction(f)) { return f; } if (f._isWrap) { return f; } if (!f._rollbar_wrapped) { f._rollbar_wrapped = function () { if (_before && _.isFunction(_before)) { _before.apply(this, arguments); } try { return f.apply(this, arguments); } catch (exc) { var e = exc; if (e && window._rollbarWrappedError !== e) { if (_.isType(e, 'string')) { e = new String(e); } e._rollbarContext = ctxFn() || {}; e._rollbarContext._wrappedSource = f.toString(); window._rollbarWrappedError = e; } throw e; } }; f._rollbar_wrapped._isWrap = true; if (f.hasOwnProperty) { for (var prop in f) { if (f.hasOwnProperty(prop) && prop !== '_rollbar_wrapped') { f._rollbar_wrapped[prop] = f[prop]; } } } } return f._rollbar_wrapped; } catch (e) { // Return the original function if the wrap fails. return f; } }; Rollbar.wrap = function (f, context) { if (_instance) { return _instance.wrap(f, context); } else { handleUninitialized(); } }; Rollbar.prototype.captureEvent = function () { var event = _.createTelemetryEvent(arguments); return this.client.captureEvent(event.type, event.metadata, event.level); }; Rollbar.captureEvent = function () { if (_instance) { return _instance.captureEvent.apply(_instance, arguments); } else { handleUninitialized(); } }; // The following two methods are used internally and are not meant for public use Rollbar.prototype.captureDomContentLoaded = function (e, ts) { if (!ts) { ts = new Date(); } return this.client.captureDomContentLoaded(ts); }; Rollbar.prototype.captureLoad = function (e, ts) { if (!ts) { ts = new Date(); } return this.client.captureLoad(ts); }; /* Internal */ function addTransformsToNotifier(notifier, rollbar, gWindow) { notifier .addTransform(transforms.handleDomException) .addTransform(transforms.handleItemWithError) .addTransform(transforms.ensureItemHasSomethingToSay) .addTransform(transforms.addBaseInfo) .addTransform(transforms.addRequestInfo(gWindow)) .addTransform(transforms.addClientInfo(gWindow)) .addTransform(transforms.addPluginInfo(gWindow)) .addTransform(transforms.addBody) .addTransform(sharedTransforms.addMessageWithError) .addTransform(sharedTransforms.addTelemetryData) .addTransform(sharedTransforms.addConfigToPayload) .addTransform(transforms.addScrubber(rollbar.scrub)) .addTransform(sharedTransforms.addPayloadOptions) .addTransform(sharedTransforms.userTransform(logger)) .addTransform(sharedTransforms.addConfiguredOptions) .addTransform(sharedTransforms.addDiagnosticKeys) .addTransform(sharedTransforms.itemToPayload); } function addPredicatesToQueue(queue) { queue .addPredicate(sharedPredicates.checkLevel) .addPredicate(predicates.checkIgnore) .addPredicate(sharedPredicates.userCheckIgnore(logger)) .addPredicate(sharedPredicates.urlIsNotBlockListed(logger)) .addPredicate(sharedPredicates.urlIsSafeListed(logger)) .addPredicate(sharedPredicates.messageIsIgnored(logger)); } Rollbar.prototype.loadFull = function () { logger.info( 'Unexpected Rollbar.loadFull() called on a Notifier instance. This can happen when Rollbar is loaded multiple times.', ); }; Rollbar.prototype._createItem = function (args) { return _.createItem(args, logger, this); }; function _getFirstFunction(args) { for (var i = 0, len = args.length; i < len; ++i) { if (_.isFunction(args[i])) { return args[i]; } } return undefined; } function _gWindow() { return ( (typeof window != 'undefined' && window) || (typeof self != 'undefined' && self) ); } var defaults = require('../defaults'); var scrubFields = require('./defaults/scrubFields'); var defaultOptions = { version: defaults.version, scrubFields: scrubFields.scrubFields, logLevel: defaults.logLevel, reportLevel: defaults.reportLevel, uncaughtErrorLevel: defaults.uncaughtErrorLevel, endpoint: defaults.endpoint, verbose: false, enabled: true, transmit: true, sendConfig: false, includeItemsInTelemetry: true, captureIp: true, inspectAnonymousErrors: true, ignoreDuplicateErrors: true, wrapGlobalEventHandlers: false, }; module.exports = Rollbar;