From 7c4743a72737bd73f05e572b409bca3cd461ce05 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Thu, 21 May 2015 15:39:15 -0500 Subject: [PATCH 1/2] Made many optimizations to bring down the file size of the library. --- README.md | 2 +- example/index.html | 4 + example/index.js | 21 ++++ gulpfile.js | 2 +- package.json | 1 + src/EventBuilder.ts | 11 +- src/ExceptionlessClient.ts | 14 ++- src/bootstrap/DefaultBootstrapper.ts | 73 ++++++++++++ src/bootstrap/NodeBootstrapper.ts | 110 +++++++++--------- src/bootstrap/WindowBootstrapper.ts | 73 ------------ src/configuration/Configuration.ts | 90 +++++++++----- src/configuration/SettingsManager.ts | 35 ++++-- src/exceptionless.ts | 14 +-- .../DefaultLastReferenceIdManager.ts | 33 ++++++ .../InMemoryLastReferenceIdManager.ts | 17 --- src/plugins/ContextData.ts | 6 +- src/plugins/EventPluginManager.ts | 4 +- .../default/ConfigurationDefaultsPlugin.ts | 4 +- src/plugins/default/DuplicateCheckerPlugin.ts | 14 --- src/plugins/default/EnvironmentInfoPlugin.ts | 15 +-- src/plugins/default/ErrorPlugin.ts | 13 ++- src/plugins/default/ModuleInfoPlugin.ts | 14 +-- src/plugins/default/ReferenceIdPlugin.ts | 4 +- src/plugins/default/RequestInfoPlugin.ts | 16 +-- src/plugins/default/SubmissionMethodPlugin.ts | 4 +- src/queue/DefaultEventQueue.ts | 93 ++++++++++----- src/services/DefaultErrorParser.ts | 53 +++++++++ ...Collector.ts => DefaultModuleCollector.ts} | 2 +- ...ctor.ts => DefaultRequestInfoCollector.ts} | 8 +- src/services/IErrorParser.ts | 2 +- src/services/NodeEnvironmentInfoCollector.ts | 30 ++--- src/services/NodeErrorParser.ts | 50 ++++---- src/services/NodeRequestInfoCollector.ts | 11 +- src/services/WebErrorParser.ts | 53 --------- src/submission/DefaultSubmissionClient.ts | 83 +++++++++---- src/submission/NodeSubmissionClient.ts | 7 +- src/submission/SubmissionClientBase.ts | 57 --------- 37 files changed, 572 insertions(+), 471 deletions(-) create mode 100644 src/bootstrap/DefaultBootstrapper.ts delete mode 100644 src/bootstrap/WindowBootstrapper.ts create mode 100644 src/lastReferenceIdManager/DefaultLastReferenceIdManager.ts delete mode 100644 src/lastReferenceIdManager/InMemoryLastReferenceIdManager.ts delete mode 100644 src/plugins/default/DuplicateCheckerPlugin.ts create mode 100644 src/services/DefaultErrorParser.ts rename src/services/{WebModuleCollector.ts => DefaultModuleCollector.ts} (93%) rename src/services/{WebRequestInfoCollector.ts => DefaultRequestInfoCollector.ts} (81%) delete mode 100644 src/services/WebErrorParser.ts delete mode 100644 src/submission/SubmissionClientBase.ts diff --git a/README.md b/README.md index d893be44..b549a08a 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ We will be adding cdn support in the near future. #### Node.js Use this method to install exceptionless into your node app. -1. Install the package by running `npm install exceptionless`. +1. Install the package by running `npm install exceptionless --save-dev`. 2. Add the exceptionless client to your app: ```javascript var client = require('exceptionless').ExceptionlessClient.default; diff --git a/example/index.html b/example/index.html index 7333ef64..429b3411 100644 --- a/example/index.html +++ b/example/index.html @@ -36,5 +36,9 @@ + + + + diff --git a/example/index.js b/example/index.js index dc5cbedf..6908c433 100644 --- a/example/index.js +++ b/example/index.js @@ -2,6 +2,27 @@ function getNonexistentData() { /* random comment */ nonexistentArray[arguments[0]]; // second random comment; } +function sendEvents(numberToSends, eventType) { + eventType = eventType || getRandomInt(0, 1); + + for (var index = 0; index < numberToSends; index++) { + switch (eventType) { + case 0: { + Exceptionless.ExceptionlessClient.default.submitLog('sendEvents', 'This is a test message', 'info'); + break; + } + case 1: { + throwIndexOutOfRange(); + break; + } + } + } +} + +function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + function throwDivisionByZero() { return divide(10, 0); } diff --git a/gulpfile.js b/gulpfile.js index 255d9c41..96961c5f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -63,7 +63,7 @@ gulp.task('exceptionless.es5', ['exceptionless.es5.umd'], function() { .pipe(concat('exceptionless.min.js')) .pipe(replace('exceptionless-js/1.0.0.0', 'exceptionless-js/' + package.version)) .pipe(replace('require(\'source-map/lib/source-map/source-map-consumer\')', 'null')) // needed for node until source maps dependency is fixed - .pipe(uglify()) + .pipe(uglify({ output: { beautify: false }})) .pipe(sourcemaps.write('.')) .pipe(gulp.dest('dist')) }); diff --git a/package.json b/package.json index 2bb4ad14..298dbc08 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "karma-cli": "0.0.4", "karma-jasmine": "~0.3.5", "rimraf": "^2.3.3", + "tracekit": "^0.2.0", "tsproject": "^0.8.10", "typescript": "1.5.0-beta" }, diff --git a/src/EventBuilder.ts b/src/EventBuilder.ts index 7564f0d9..963cb5c8 100644 --- a/src/EventBuilder.ts +++ b/src/EventBuilder.ts @@ -7,7 +7,7 @@ import { EventPluginContext } from './plugins/EventPluginContext'; import { Utils } from 'Utils'; export class EventBuilder { - private _validIdentifierErrorMessage:string = "must contain between 8 and 100 alphanumeric or '-' characters."; + private _validIdentifierErrorMessage:string = "must contain between 8 and 100 alphanumeric or '-' characters."; // optimization for minifier. public target:IEvent; public client:ExceptionlessClient; @@ -20,7 +20,7 @@ export class EventBuilder { } public setType(type:string): EventBuilder { - if (!!type && type.length > 0) { + if (!!type) { this.target.type = type; } @@ -28,7 +28,7 @@ export class EventBuilder { } public setSource(source:string): EventBuilder { - if (!!source && source.length > 0) { + if (!!source) { this.target.source = source; } @@ -54,7 +54,7 @@ export class EventBuilder { } public setMessage(message:string): EventBuilder { - if (!!message && message.length > 0) { + if (!!message) { this.target.message = message; } @@ -118,7 +118,6 @@ export class EventBuilder { return this; } - //todo: rename this public addRequestInfo(request:Object): EventBuilder { if (!!request) { this.pluginContextData['@request'] = request; @@ -132,7 +131,7 @@ export class EventBuilder { } private isValidIdentifier(value:string): boolean { - if (!value || !value.length) { + if (!value) { return true; } diff --git a/src/ExceptionlessClient.ts b/src/ExceptionlessClient.ts index d8a29b9f..4f99d156 100644 --- a/src/ExceptionlessClient.ts +++ b/src/ExceptionlessClient.ts @@ -16,9 +16,6 @@ export class ExceptionlessClient { constructor(settings:IConfigurationSettings); constructor(apiKey:string, serverUrl?:string); constructor(settingsOrApiKey?:IConfigurationSettings|string, serverUrl?:string) { - // TODO: populate this in a plugin.. - //var settings = this.getSettingsFromScriptTag() || {}; - if (typeof settingsOrApiKey !== 'object') { this.config = new Configuration(settingsOrApiKey); } else { @@ -197,7 +194,18 @@ export class ExceptionlessClient { return this.config.lastReferenceIdManager.getLast(); } + /** + * The default ExceptionlessClient instance. + * @type {ExceptionlessClient} + * @private + */ private static _instance:ExceptionlessClient = null; + + + /** + * The default ExceptionlessClient instance. + * @type {ExceptionlessClient} + */ public static get default() { if(ExceptionlessClient._instance === null) { ExceptionlessClient._instance = new ExceptionlessClient(null); diff --git a/src/bootstrap/DefaultBootstrapper.ts b/src/bootstrap/DefaultBootstrapper.ts new file mode 100644 index 00000000..eb81a333 --- /dev/null +++ b/src/bootstrap/DefaultBootstrapper.ts @@ -0,0 +1,73 @@ +import { IBootstrapper } from 'IBootstrapper'; +import { Configuration } from '../configuration/Configuration'; +import { IConfigurationSettings } from '../configuration/IConfigurationSettings'; +import { IError } from '../models/IError'; +import { DefaultErrorParser } from '../services/DefaultErrorParser'; +import { DefaultModuleCollector } from '../services/DefaultModuleCollector'; +import { DefaultRequestInfoCollector } from '../services/DefaultRequestInfoCollector'; +import { DefaultSubmissionClient } from '../submission/DefaultSubmissionClient'; +import { ExceptionlessClient } from '../ExceptionlessClient'; +import { Utils } from '../Utils'; + +export class DefaultBootstrapper implements IBootstrapper { + public register(): void { + function getDefaultsSettingsFromScriptTag(): IConfigurationSettings { + if (!document || !document.getElementsByTagName) { + return null; + } + + var scripts = document.getElementsByTagName('script'); + for (var index = 0; index < scripts.length; index++) { + if (scripts[index].src && scripts[index].src.indexOf('/exceptionless') > -1) { + return Utils.parseQueryString(scripts[index].src.split('?').pop()); + } + } + return null; + } + + function processUnhandledException(stackTrace:TraceKit.StackTrace, options?:any): void { + var builder = ExceptionlessClient.default.createUnhandledException(new Error(stackTrace.message || (options || {}).status || 'Script error'), 'onerror'); + builder.pluginContextData['@@_TraceKit.StackTrace'] = stackTrace; + builder.submit(); + } + + function processJQueryAjaxError(event, xhr, settings, error:Error): void { + var client = ExceptionlessClient.default; + if (xhr.status === 404) { + client.submitNotFound(settings.url); + } else if (xhr.status !== 401) { + client.createUnhandledException(error, 'JQuery.ajaxError') + .setSource(settings.url) + .setProperty('status', xhr.status) + .setProperty('request', settings.data) + .setProperty('response', xhr.responseText && xhr.responseText.slice && xhr.responseText.slice(0, 1024)) + .submit(); + } + } + + if (typeof window === 'undefined' || typeof document === 'undefined') { + return; + } + + var defaults = Configuration.defaults; + var settings = getDefaultsSettingsFromScriptTag(); + if (settings && (settings.apiKey || settings.serverUrl)) { + defaults.apiKey = settings.apiKey; + defaults.serverUrl = settings.serverUrl; + } + + defaults.errorParser = new DefaultErrorParser(); + defaults.moduleCollector = new DefaultModuleCollector(); + defaults.requestInfoCollector = new DefaultRequestInfoCollector(); + defaults.submissionClient = new DefaultSubmissionClient(); + + TraceKit.report.subscribe(processUnhandledException); + TraceKit.extendToAsynchronousCallbacks(); + + if (typeof $ !== 'undefined' && $(document)) { + $(document).ajaxError(processJQueryAjaxError); + } + } +} + +declare var $; diff --git a/src/bootstrap/NodeBootstrapper.ts b/src/bootstrap/NodeBootstrapper.ts index 1c80762b..3e251c4b 100644 --- a/src/bootstrap/NodeBootstrapper.ts +++ b/src/bootstrap/NodeBootstrapper.ts @@ -11,78 +11,82 @@ import { Utils } from '../Utils'; export class NodeBootstrapper implements IBootstrapper { public register(): void { + const beforeExit:string = 'beforeExit'; + const uncaughtException:string = 'uncaughtException'; + if (!(typeof window === 'undefined' && typeof global !== 'undefined' && {}.toString.call(global) === '[object global]')) { return; } - var configDefaults = Configuration.defaults; - configDefaults.environmentInfoCollector = new NodeEnvironmentInfoCollector(); - configDefaults.errorParser = new NodeErrorParser(); - configDefaults.requestInfoCollector = new NodeRequestInfoCollector(); - configDefaults.submissionClient = new NodeSubmissionClient(); + var defaults = Configuration.defaults; + defaults.environmentInfoCollector = new NodeEnvironmentInfoCollector(); + defaults.errorParser = new NodeErrorParser(); + defaults.requestInfoCollector = new NodeRequestInfoCollector(); + defaults.submissionClient = new NodeSubmissionClient(); - process.on('uncaughtException', function(error:Error) { - ExceptionlessClient.default.submitUnhandledException(error, 'uncaughtException'); + process.on(uncaughtException, function (error:Error) { + ExceptionlessClient.default.submitUnhandledException(error, uncaughtException); }); - process.on('beforeExit', (code:number) => { - var client = ExceptionlessClient.default; + process.on(beforeExit, function (code:number) { + /** + * exit codes: https://nodejs.org/api/process.html#process_event_exit + */ + function getExitCodeReason(code:number): string { + if (code === 1) { + return 'Uncaught Fatal Exception'; + } - var message = this.getExitCodeReason(code); - if (message !== null) { - client.submitLog('beforeExit', message, 'Error') - } + if (code === 3) { + return 'Internal JavaScript Parse Error'; + } - client.config.queue.process() - }); - } + if (code === 4) { + return 'Internal JavaScript Evaluation Failure'; + } - // exit codes: https://nodejs.org/api/process.html#process_event_exit - private getExitCodeReason(code:number): string { - if (code === 1) { - return 'Uncaught Fatal Exception'; - } + if (code === 5) { + return 'Fatal Exception'; + } - if (code === 3) { - return 'Internal JavaScript Parse Error'; - } + if (code === 6) { + return 'Non-function Internal Exception Handler '; + } - if (code === 4) { - return 'Internal JavaScript Evaluation Failure'; - } + if (code === 7) { + return 'Internal Exception Handler Run-Time Failure'; + } - if (code === 5) { - return 'Fatal Exception'; - } + if (code === 8) { + return 'Uncaught Exception'; + } - if (code === 6) { - return 'Non-function Internal Exception Handler '; - } + if (code === 9) { + return 'Invalid Argument'; + } - if (code === 7) { - return 'Internal Exception Handler Run-Time Failure'; - } + if (code === 10) { + return 'Internal JavaScript Run-Time Failure'; + } - if (code === 8) { - return 'Uncaught Exception'; - } - - if (code === 9) { - return 'Invalid Argument'; - } + if (code === 12) { + return 'Invalid Debug Argument'; + } - if (code === 10) { - return 'Internal JavaScript Run-Time Failure'; - } + if (code > 128) { + return 'Signal Exits'; + } - if (code === 12) { - return 'Invalid Debug Argument'; - } + return null; + } - if (code > 128) { - return 'Signal Exits'; - } + var client = ExceptionlessClient.default; + var message = getExitCodeReason(code); + if (message !== null) { + client.submitLog(beforeExit, message, 'Error') + } - return null; + client.config.queue.process() + }); } } diff --git a/src/bootstrap/WindowBootstrapper.ts b/src/bootstrap/WindowBootstrapper.ts deleted file mode 100644 index d3216540..00000000 --- a/src/bootstrap/WindowBootstrapper.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { IBootstrapper } from 'IBootstrapper'; -import { Configuration } from '../configuration/Configuration'; -import { IConfigurationSettings } from '../configuration/IConfigurationSettings'; -import { IError } from '../models/IError'; -import { WebErrorParser } from '../services/WebErrorParser'; -import { WebModuleCollector } from '../services/WebModuleCollector'; -import { WebRequestInfoCollector } from '../services/WebRequestInfoCollector'; -import { DefaultSubmissionClient } from '../submission/DefaultSubmissionClient'; -import { ExceptionlessClient } from '../ExceptionlessClient'; -import { Utils } from '../Utils'; - -export class WindowBootstrapper implements IBootstrapper { - public register(): void { - if (typeof window === 'undefined' || typeof document === 'undefined') { - return; - } - - var configDefaults = Configuration.defaults; - var settings = this.getDefaultsSettingsFromScriptTag(); - if (settings && (settings.apiKey || settings.serverUrl)) { - configDefaults.apiKey = settings.apiKey; - configDefaults.serverUrl = settings.serverUrl; - } - - configDefaults.errorParser = new WebErrorParser(); - configDefaults.moduleCollector = new WebModuleCollector(); - configDefaults.requestInfoCollector = new WebRequestInfoCollector(); - configDefaults.submissionClient = new DefaultSubmissionClient(); - - TraceKit.report.subscribe(this.processUnhandledException); - TraceKit.extendToAsynchronousCallbacks(); - - if (typeof $ !== 'undefined' && $(document)) { - $(document).ajaxError(this.processJQueryAjaxError); - } - } - - private getDefaultsSettingsFromScriptTag(): IConfigurationSettings { - if (!document || !document.getElementsByTagName) { - return null; - } - - var scripts = document.getElementsByTagName('script'); - for (var index = 0; index < scripts.length; index++) { - if (scripts[index].src && scripts[index].src.indexOf('/exceptionless') > -1) { - return Utils.parseQueryString(scripts[index].src.split('?').pop()); - } - } - return null; - } - - private processUnhandledException(stackTrace:TraceKit.StackTrace, options?:any): void { - var builder = ExceptionlessClient.default.createUnhandledException(new Error(stackTrace.message || (options || {}).status || 'Script error'), 'onerror'); - builder.pluginContextData['@@_TraceKit.StackTrace'] = stackTrace; - builder.submit(); - } - - private processJQueryAjaxError(event, xhr, settings, error:Error): void { - var client = ExceptionlessClient.default; - if (xhr.status === 404) { - client.submitNotFound(settings.url); - } else if (xhr.status !== 401) { - client.createUnhandledException(error, 'JQuery.ajaxError') - .setSource(settings.url) - .setProperty('status', xhr.status) - .setProperty('request', settings.data) - .setProperty('response', xhr.responseText && xhr.responseText.slice ? xhr.responseText.slice(0, 1024) : undefined) - .submit(); - } - } -} - -declare var $; diff --git a/src/configuration/Configuration.ts b/src/configuration/Configuration.ts index b39c13d1..6bfbfe89 100644 --- a/src/configuration/Configuration.ts +++ b/src/configuration/Configuration.ts @@ -1,7 +1,7 @@ import { IConfigurationSettings } from './IConfigurationSettings'; import { SettingsManager } from './SettingsManager'; import { ILastReferenceIdManager } from '../lastReferenceIdManager/ILastReferenceIdManager'; -import { InMemoryLastReferenceIdManager } from '../lastReferenceIdManager/InMemoryLastReferenceIdManager'; +import { DefaultLastReferenceIdManager } from '../lastReferenceIdManager/DefaultLastReferenceIdManager'; import { ConsoleLog } from '../logging/ConsoleLog'; import { ILog } from '../logging/ILog'; import { NullLog } from '../logging/NullLog'; @@ -22,12 +22,6 @@ import { ISubmissionClient } from '../submission/ISubmissionClient'; import { Utils } from '../Utils'; export class Configuration implements IConfigurationSettings { - private _apiKey:string; - private _enabled:boolean = true; - private _serverUrl:string = 'https://collector.exceptionless.io'; - private _dataExclusions:string[] = []; - private _plugins:IEventPlugin[] = []; - /** * A default list of tags that will automatically be added to every * report submitted to the server. @@ -44,9 +38,17 @@ export class Configuration implements IConfigurationSettings { */ public defaultData:Object = {}; + /** + * Whether the client is currently enabled or not. If it is disabled, + * submitted errors will be discarded and no data will be sent to the server. + * + * @returns {boolean} + */ + public enabled:boolean = true; + public environmentInfoCollector:IEnvironmentInfoCollector; public errorParser:IErrorParser; - public lastReferenceIdManager:ILastReferenceIdManager = new InMemoryLastReferenceIdManager(); + public lastReferenceIdManager:ILastReferenceIdManager = new DefaultLastReferenceIdManager(); public log:ILog; public moduleCollector:IModuleCollector; public requestInfoCollector:IRequestInfoCollector; @@ -80,7 +82,7 @@ export class Configuration implements IConfigurationSettings { this.environmentInfoCollector = inject(configSettings.environmentInfoCollector); this.errorParser = inject(configSettings.errorParser); - this.lastReferenceIdManager = inject(configSettings.lastReferenceIdManager) || new InMemoryLastReferenceIdManager(); + this.lastReferenceIdManager = inject(configSettings.lastReferenceIdManager) || new DefaultLastReferenceIdManager(); this.moduleCollector = inject(configSettings.moduleCollector); this.requestInfoCollector = inject(configSettings.requestInfoCollector); this.submissionBatchSize = inject(configSettings.submissionBatchSize) || 50; @@ -92,6 +94,13 @@ export class Configuration implements IConfigurationSettings { EventPluginManager.addDefaultPlugins(this); } + /** + * The API key that will be used when sending events to the server. + * @type {string} + * @private + */ + private _apiKey:string; + /** * The API key that will be used when sending events to the server. * @returns {string} @@ -117,6 +126,13 @@ export class Configuration implements IConfigurationSettings { return !!this.apiKey && this.apiKey.length >= 10; } + /** + * The server url that all events will be sent to. + * @type {string} + * @private + */ + private _serverUrl:string = 'https://collector.exceptionless.io'; + /** * The server url that all events will be sent to. * @returns {string} @@ -130,21 +146,18 @@ export class Configuration implements IConfigurationSettings { * @param value */ public set serverUrl(value:string) { - if (!!value && value.length > 0) { + if (!!value) { this._serverUrl = value; this.log.info(`serverUrl: ${this._serverUrl}`); } } /** - * Whether the client is currently enabled or not. If it is disabled, - * submitted errors will be discarded and no data will be sent to the server. - * - * @returns {boolean} + * A list of exclusion patterns. + * @type {Array} + * @private */ - public get enabled():boolean { - return this._enabled; - } + private _dataExclusions:string[] = []; /** * A list of exclusion patterns that will automatically remove any data that @@ -173,6 +186,13 @@ export class Configuration implements IConfigurationSettings { this._dataExclusions = Utils.addRange(this._dataExclusions, ...exclusions); } + /** + * The list of plugins that will be used in this configuration. + * @type {Array} + * @private + */ + private _plugins:IEventPlugin[] = []; + /** * The list of plugins that will be used in this configuration. * @returns {IEventPlugin[]} @@ -199,7 +219,7 @@ export class Configuration implements IConfigurationSettings { public addPlugin(pluginOrName:IEventPlugin|string, priority?:number, pluginAction?:(context:EventPluginContext, next?:() => void) => void): void { var plugin:IEventPlugin = !!pluginAction ? { name: pluginOrName, priority: priority, run: pluginAction } : pluginOrName; if (!plugin || !plugin.run) { - this.log.error('Add plugin failed: No run method was defined.'); + this.log.error('Add plugin failed: Run method not defined'); return; } @@ -212,15 +232,16 @@ export class Configuration implements IConfigurationSettings { } var pluginExists:boolean = false; - for (var index = 0; index < this._plugins.length; index++) { - if (this._plugins[index].name === plugin.name) { + var plugins = this._plugins; // optimization for minifier. + for (var index = 0; index < plugins.length; index++) { + if (plugins[index].name === plugin.name) { pluginExists = true; break; } } if (!pluginExists) { - this._plugins.push(plugin); + plugins.push(plugin); } } @@ -238,13 +259,14 @@ export class Configuration implements IConfigurationSettings { public removePlugin(pluginOrName:IEventPlugin|string): void { var name:string = typeof pluginOrName === 'string' ? pluginOrName : pluginOrName.name; if (!name) { - this.log.error('Remove plugin failed: No plugin name was defined.'); + this.log.error('Remove plugin failed: Plugin name not defined'); return; } - for (var index = 0; index < this._plugins.length; index++) { - if (this._plugins[index].name === name) { - this._plugins.splice(index, 1); + var plugins = this._plugins; // optimization for minifier. + for (var index = 0; index < plugins.length; index++) { + if (plugins[index].name === name) { + plugins.splice(index, 1); break; } } @@ -255,7 +277,7 @@ export class Configuration implements IConfigurationSettings { * @param version */ public setVersion(version:string): void { - if (!!version && version.length > 0) { + if (!!version) { this.defaultData['@version'] = version; } } @@ -264,12 +286,14 @@ export class Configuration implements IConfigurationSettings { public setUserIdentity(identity:string): void; public setUserIdentity(identity:string, name:string): void; public setUserIdentity(userInfoOrIdentity:IUserInfo|string, name?:string): void { + const user:string = '@user'; // optimization for minifier. var userInfo:IUserInfo = typeof userInfoOrIdentity !== 'string' ? userInfoOrIdentity : { identity: userInfoOrIdentity, name: name }; + var shouldRemove:boolean = !userInfo || (!userInfo.identity && !userInfo.name); if (shouldRemove) { - delete this.defaultData['@user']; + delete this.defaultData[user]; } else { - this.defaultData['@user'] = userInfo; + this.defaultData[user] = userInfo; } this.log.info(`user identity: ${shouldRemove ? 'null' : userInfo.identity}`); @@ -295,7 +319,17 @@ export class Configuration implements IConfigurationSettings { this.log = new ConsoleLog(); } + /** + * The default configuration settings that are applied to new configuration instances. + * @type {IConfigurationSettings} + * @private + */ private static _defaultSettings:IConfigurationSettings = null; + + /** + * The default configuration settings that are applied to new configuration instances. + * @returns {IConfigurationSettings} + */ public static get defaults() { if (Configuration._defaultSettings === null) { Configuration._defaultSettings = {}; diff --git a/src/configuration/SettingsManager.ts b/src/configuration/SettingsManager.ts index e92beaff..9d4ee2e5 100644 --- a/src/configuration/SettingsManager.ts +++ b/src/configuration/SettingsManager.ts @@ -3,12 +3,24 @@ import { SettingsResponse } from '../submission/SettingsResponse'; import { Utils } from '../Utils'; export class SettingsManager { + /** + * The configuration settings path. + * @type {string} + * @private + */ private static _configPath:string = 'ex-server-settings.json'; + + /** + * A list of handlers that will be fired when the settings change. + * @type {Array} + * @private + */ private static _handlers:{ (config:Configuration):void }[] = []; private static changed(config:Configuration) { - for (var index = 0; index < this._handlers.length; index++) { - this._handlers[index](config); + var handlers = this._handlers; // optimization for minifier. + for (var index = 0; index < handlers.length; index++) { + handlers[index](config); } } @@ -27,14 +39,12 @@ export class SettingsManager { } public static checkVersion(version:number, config:Configuration):void { - if (isNaN(version) || version <= 0) { - return; - } - - var savedConfigVersion = parseInt(config.storage.get(`${this._configPath}-version`, 1)[0]); - if (isNaN(savedConfigVersion) || version > savedConfigVersion) { - config.log.info(`Updating settings from v${(!isNaN(savedConfigVersion) ? savedConfigVersion : 0)} to v${version}`); - this.updateSettings(config); + if (version) { + var savedConfigVersion = parseInt(config.storage.get(`${this._configPath}-version`, 1)[0]); + if (isNaN(savedConfigVersion) || version > savedConfigVersion) { + config.log.info(`Updating settings from v${(!isNaN(savedConfigVersion) ? savedConfigVersion : 0)} to v${version}`); + this.updateSettings(config); + } } } @@ -62,8 +72,9 @@ export class SettingsManager { delete config.settings[key]; } - config.storage.save(`${this._configPath}-version`, response.settingsVersion); - config.storage.save(this._configPath, response.settings); + var path = SettingsManager._configPath; // optimization for minifier. + config.storage.save(`${path}-version`, response.settingsVersion); + config.storage.save(path, response.settings); config.log.info('Updated settings'); this.changed(config); diff --git a/src/exceptionless.ts b/src/exceptionless.ts index bbf99549..53ca44b8 100644 --- a/src/exceptionless.ts +++ b/src/exceptionless.ts @@ -1,11 +1,11 @@ import { IBootstrapper } from './bootstrap/IBootstrapper'; import { NodeBootstrapper } from './bootstrap/NodeBootstrapper'; -import { WindowBootstrapper } from './bootstrap/WindowBootstrapper'; +import { DefaultBootstrapper } from './bootstrap/DefaultBootstrapper'; import { Configuration } from './configuration/Configuration'; import { IConfigurationSettings } from './configuration/IConfigurationSettings'; import { SettingsManager } from './configuration/SettingsManager'; import { ILastReferenceIdManager } from './lastReferenceIdManager/ILastReferenceIdManager'; -import { InMemoryLastReferenceIdManager } from './lastReferenceIdManager/InMemoryLastReferenceIdManager'; +import { DefaultLastReferenceIdManager } from './lastReferenceIdManager/DefaultLastReferenceIdManager'; import { ConsoleLog } from './logging/ConsoleLog'; import { ILog } from './logging/ILog'; import { NullLog } from './logging/NullLog'; @@ -26,7 +26,6 @@ import { EventPluginContext } from './plugins/EventPluginContext'; import { EventPluginManager } from './plugins/EventPluginManager'; import { IEventPlugin } from './plugins/IEventPlugin'; import { ConfigurationDefaultsPlugin } from './plugins/default/ConfigurationDefaultsPlugin'; -import { DuplicateCheckerPlugin } from './plugins/default/DuplicateCheckerPlugin'; import { ErrorPlugin } from './plugins/default/ErrorPlugin'; import { ModuleInfoPlugin } from './plugins/default/ModuleInfoPlugin'; import { ReferenceIdPlugin } from './plugins/default/ReferenceIdPlugin'; @@ -42,21 +41,20 @@ import { IRequestInfoCollector } from './services/IRequestInfoCollector'; import { NodeEnvironmentInfoCollector } from './services/NodeEnvironmentInfoCollector'; import { NodeErrorParser } from './services/NodeErrorParser'; import { NodeRequestInfoCollector } from './services/NodeRequestInfoCollector'; -import { WebErrorParser } from './services/WebErrorParser'; -import { WebModuleCollector } from './services/WebModuleCollector'; -import { WebRequestInfoCollector } from './services/WebRequestInfoCollector'; +import { DefaultErrorParser } from './services/DefaultErrorParser'; +import { DefaultModuleCollector } from './services/DefaultModuleCollector'; +import { DefaultRequestInfoCollector } from './services/DefaultRequestInfoCollector'; import { InMemoryStorage } from './storage/InMemoryStorage'; import { IStorage } from './storage/IStorage'; import { DefaultSubmissionClient } from './submission/DefaultSubmissionClient'; import { ISubmissionClient } from './submission/ISubmissionClient'; import { NodeSubmissionClient } from './submission/NodeSubmissionClient'; import { SettingsResponse } from './submission/SettingsResponse'; -import { SubmissionClientBase } from './submission/SubmissionClientBase'; import { SubmissionResponse } from './submission/SubmissionResponse'; import { EventBuilder } from 'EventBuilder'; import { ExceptionlessClient } from 'ExceptionlessClient'; import { Utils } from 'Utils'; new NodeBootstrapper().register(); -new WindowBootstrapper().register(); +new DefaultBootstrapper().register(); (Error).stackTraceLimit = Infinity; diff --git a/src/lastReferenceIdManager/DefaultLastReferenceIdManager.ts b/src/lastReferenceIdManager/DefaultLastReferenceIdManager.ts new file mode 100644 index 00000000..cb6f8305 --- /dev/null +++ b/src/lastReferenceIdManager/DefaultLastReferenceIdManager.ts @@ -0,0 +1,33 @@ +import { ILastReferenceIdManager } from './ILastReferenceIdManager'; + +export class DefaultLastReferenceIdManager implements ILastReferenceIdManager { + /** + * Gets the last event's reference id that was submitted to the server. + * @type {string} + * @private + */ + private _lastReferenceId:string = null; + + /** + * Gets the last event's reference id that was submitted to the server. + * @returns {string} + */ + getLast(): string { + return this._lastReferenceId; + } + + /** + * Clears the last event's reference id. + */ + clearLast():void { + this._lastReferenceId = null; + } + + /** + * Sets the last event's reference id. + * @param eventId + */ + setLast(eventId:string):void { + this._lastReferenceId = eventId; + } +} diff --git a/src/lastReferenceIdManager/InMemoryLastReferenceIdManager.ts b/src/lastReferenceIdManager/InMemoryLastReferenceIdManager.ts deleted file mode 100644 index cd20a8ce..00000000 --- a/src/lastReferenceIdManager/InMemoryLastReferenceIdManager.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ILastReferenceIdManager } from './ILastReferenceIdManager'; - -export class InMemoryLastReferenceIdManager implements ILastReferenceIdManager { - private _lastReferenceId:string = null; - - getLast(): string { - return this._lastReferenceId; - } - - clearLast():void { - this._lastReferenceId = null; - } - - setLast(eventId:string):void { - this._lastReferenceId = eventId; - } -} diff --git a/src/plugins/ContextData.ts b/src/plugins/ContextData.ts index 41b3fb38..c670c459 100644 --- a/src/plugins/ContextData.ts +++ b/src/plugins/ContextData.ts @@ -10,7 +10,7 @@ export class ContextData { } public getException(): Error { - return this.hasException ? this['@@_Exception'] : null; + return this['@@_Exception'] || null; } public markAsUnhandledError(): void { @@ -22,12 +22,12 @@ export class ContextData { } public setSubmissionMethod(method:string): void { - if (method && method.length > 0) { + if (method) { this['@@_SubmissionMethod'] = method; } } public getSubmissionMethod(): string { - return !!this['@@_SubmissionMethod'] ? this['@@_SubmissionMethod'] : null; + return this['@@_SubmissionMethod'] || null; } } diff --git a/src/plugins/EventPluginManager.ts b/src/plugins/EventPluginManager.ts index e585f62d..cdda0285 100644 --- a/src/plugins/EventPluginManager.ts +++ b/src/plugins/EventPluginManager.ts @@ -3,7 +3,6 @@ import { IEventPlugin } from './IEventPlugin'; import { EventPluginContext } from './EventPluginContext'; import { ConfigurationDefaultsPlugin } from './default/ConfigurationDefaultsPlugin'; import { ErrorPlugin } from './default/ErrorPlugin'; -import { DuplicateCheckerPlugin } from './default/DuplicateCheckerPlugin'; import { ModuleInfoPlugin } from './default/ModuleInfoPlugin'; import { RequestInfoPlugin } from './default/RequestInfoPlugin'; import { EnvironmentInfoPlugin } from './default/EnvironmentInfoPlugin'; @@ -28,7 +27,7 @@ export class EventPluginManager { }; }; - var plugins:IEventPlugin[] = context.client.config.plugins; + var plugins:IEventPlugin[] = context.client.config.plugins; // optimization for minifier. var wrappedPlugins:{ (): void }[] = []; if (!!callback) { wrappedPlugins[plugins.length] = wrap({ name: 'cb', priority: 9007199254740992, run: callback }, null); @@ -44,7 +43,6 @@ export class EventPluginManager { public static addDefaultPlugins(config:Configuration): void { config.addPlugin(new ConfigurationDefaultsPlugin()); config.addPlugin(new ErrorPlugin()); - config.addPlugin(new DuplicateCheckerPlugin()); config.addPlugin(new ModuleInfoPlugin()); config.addPlugin(new RequestInfoPlugin()); config.addPlugin(new EnvironmentInfoPlugin()); diff --git a/src/plugins/default/ConfigurationDefaultsPlugin.ts b/src/plugins/default/ConfigurationDefaultsPlugin.ts index b9bf478b..8edb4156 100644 --- a/src/plugins/default/ConfigurationDefaultsPlugin.ts +++ b/src/plugins/default/ConfigurationDefaultsPlugin.ts @@ -21,8 +21,6 @@ export class ConfigurationDefaultsPlugin implements IEventPlugin { } } - if (next) { - next(); - } + next && next(); } } diff --git a/src/plugins/default/DuplicateCheckerPlugin.ts b/src/plugins/default/DuplicateCheckerPlugin.ts deleted file mode 100644 index a8963cd9..00000000 --- a/src/plugins/default/DuplicateCheckerPlugin.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { IEventPlugin } from '../IEventPlugin'; -import { EventPluginContext } from '../EventPluginContext'; - -export class DuplicateCheckerPlugin implements IEventPlugin { - public priority:number = 50; - public name:string = 'DuplicateCheckerPlugin'; - - public run(context:EventPluginContext, next?:() => void): void { - // TODO: Implement - if (next) { - next(); - } - } -} diff --git a/src/plugins/default/EnvironmentInfoPlugin.ts b/src/plugins/default/EnvironmentInfoPlugin.ts index 5610bcd6..024179ea 100644 --- a/src/plugins/default/EnvironmentInfoPlugin.ts +++ b/src/plugins/default/EnvironmentInfoPlugin.ts @@ -7,15 +7,16 @@ export class EnvironmentInfoPlugin implements IEventPlugin { public name:string = 'EnvironmentInfoPlugin'; public run(context:EventPluginContext, next?:() => void): void { - if (!context.event.data['@environment'] && context.client.config.environmentInfoCollector) { - var ei = context.client.config.environmentInfoCollector.getEnvironmentInfo(context); - if (!!ei) { - context.event.data['@environment'] = ei; + const environment:string = '@environment'; // optimization for minifier. + + var collector = context.client.config.environmentInfoCollector; + if (!context.event.data[environment] && collector) { + var environmentInfo = collector.getEnvironmentInfo(context); + if (!!environmentInfo) { + context.event.data[environment] = environmentInfo; } } - if (next) { - next(); - } + next && next(); } } diff --git a/src/plugins/default/ErrorPlugin.ts b/src/plugins/default/ErrorPlugin.ts index 206413ba..a2b52dc4 100644 --- a/src/plugins/default/ErrorPlugin.ts +++ b/src/plugins/default/ErrorPlugin.ts @@ -6,22 +6,25 @@ export class ErrorPlugin implements IEventPlugin { public name:string = 'ErrorPlugin'; public run(context:EventPluginContext, next?:() => void): void { + const error:string = '@error'; // optimization for minifier. + var exception = context.contextData.getException(); if (!!exception) { context.event.type = 'error'; - if (!context.event.data['@error']) { + if (!context.event.data[error]) { var parser = context.client.config.errorParser; if (!parser) { throw new Error('No error parser was defined.'); } - parser.parse(context, exception); + var result = parser.parse(context, exception); + if (!!result) { + context.event.data[error] = result; + } } } - if (next) { - next(); - } + next && next(); } } diff --git a/src/plugins/default/ModuleInfoPlugin.ts b/src/plugins/default/ModuleInfoPlugin.ts index 4f7fdaac..9016763f 100644 --- a/src/plugins/default/ModuleInfoPlugin.ts +++ b/src/plugins/default/ModuleInfoPlugin.ts @@ -8,16 +8,16 @@ export class ModuleInfoPlugin implements IEventPlugin { public name:string = 'ModuleInfoPlugin'; public run(context:EventPluginContext, next?:() => void): void { - var moduleCollector = context.client.config.moduleCollector; - if (context.event.data['@error'] && !context.event.data['@error'].modules && !!moduleCollector) { - var modules = moduleCollector.getModules(context); + const error:string = '@error'; // optimization for minifier. + + var collector = context.client.config.moduleCollector; + if (context.event.data[error] && !context.event.data['@error'].modules && !!collector) { + var modules = collector.getModules(context); if (modules && modules.length > 0) { - context.event.data['@error'].modules = modules; + context.event.data[error].modules = modules; } } - if (next) { - next(); - } + next && next(); } } diff --git a/src/plugins/default/ReferenceIdPlugin.ts b/src/plugins/default/ReferenceIdPlugin.ts index 5ebc39e1..681f2053 100644 --- a/src/plugins/default/ReferenceIdPlugin.ts +++ b/src/plugins/default/ReferenceIdPlugin.ts @@ -11,8 +11,6 @@ export class ReferenceIdPlugin implements IEventPlugin { context.event.reference_id = Utils.guid().replace('-', '').substring(0, 10); } - if (next) { - next(); - } + next && next(); } } diff --git a/src/plugins/default/RequestInfoPlugin.ts b/src/plugins/default/RequestInfoPlugin.ts index 8de7090a..31d69c93 100644 --- a/src/plugins/default/RequestInfoPlugin.ts +++ b/src/plugins/default/RequestInfoPlugin.ts @@ -7,16 +7,16 @@ export class RequestInfoPlugin implements IEventPlugin { public name:string = 'RequestInfoPlugin'; public run(context:EventPluginContext, next?:() => void): void { - var requestInfoCollector = context.client.config.requestInfoCollector; - if (!context.event.data['@request'] && !!requestInfoCollector) { - var ri = requestInfoCollector.getRequestInfo(context); - if (!!ri) { - context.event.data['@request'] = ri; + const request:string = '@request'; // optimization for minifier. + + var collector = context.client.config.requestInfoCollector; + if (!context.event.data[request] && !!collector) { + var requestInfo = collector.getRequestInfo(context); + if (!!requestInfo) { + context.event.data[request] = requestInfo; } } - if (next) { - next(); - } + next && next(); } } diff --git a/src/plugins/default/SubmissionMethodPlugin.ts b/src/plugins/default/SubmissionMethodPlugin.ts index 83398921..dbb28aa4 100644 --- a/src/plugins/default/SubmissionMethodPlugin.ts +++ b/src/plugins/default/SubmissionMethodPlugin.ts @@ -11,8 +11,6 @@ export class SubmissionMethodPlugin implements IEventPlugin { context.event.data['@submission_method'] = submissionMethod; } - if (next) { - next(); - } + next && next(); } } diff --git a/src/queue/DefaultEventQueue.ts b/src/queue/DefaultEventQueue.ts index fa0f1e02..5b9f7d06 100644 --- a/src/queue/DefaultEventQueue.ts +++ b/src/queue/DefaultEventQueue.ts @@ -1,14 +1,44 @@ import { Configuration } from '../configuration/Configuration'; +import { ILog } from '../logging/ILog'; import { SubmissionResponse } from '../submission/SubmissionResponse'; import { IEvent } from '../models/IEvent'; import { IEventQueue } from '../queue/IEventQueue'; import { Utils } from '../Utils'; export class DefaultEventQueue implements IEventQueue { + /** + * The configuration object. + * @type {Configuration} + * @private + */ private _config:Configuration; + + /** + * Suspends processing until the specified time. + * @type {Date} + * @private + */ private _suspendProcessingUntil:Date; + + /** + * Discards queued items until the specified time. + * @type {Date} + * @private + */ private _discardQueuedItemsUntil:Date; + + /** + * Returns true if the queue is processing. + * @type {boolean} + * @private + */ private _processingQueue:boolean = false; + + /** + * Processes the queue every xx seconds. + * @type {Timer} + * @private + */ private _queueTimer:any; constructor(config:Configuration) { @@ -16,69 +46,76 @@ export class DefaultEventQueue implements IEventQueue { } public enqueue(event:IEvent): void { + var config:Configuration = this._config; // Optmization for minifier. this.ensureQueueTimer(); if (this.areQueuedItemsDiscarded()) { - this._config.log.info('Queue items are currently being discarded. The event will not be queued.'); + config.log.info('Queue items are currently being discarded. The event will not be queued.'); return; } var key = `${this.queuePath()}-${new Date().toJSON()}-${Utils.randomNumber()}`; - this._config.log.info(`Enqueuing event: ${key} type=${event.type} ${!!event.reference_id ? 'refid=' + event.reference_id : ''}`); - this._config.storage.save(key, event); + config.log.info(`Enqueuing event: ${key} type=${event.type} ${!!event.reference_id ? 'refid=' + event.reference_id : ''}`); + config.storage.save(key, event); } public process(): void { + const queueNotProcessed:string = 'The queue will not be processed.'; // optimization for minifier. + var config:Configuration = this._config; // Optmization for minifier. + var log:ILog = config.log; // Optmization for minifier. + this.ensureQueueTimer(); if (this._processingQueue) { return; } - var queueNotProcessed = 'The queue will not be processed.'; - this._config.log.info('Processing queue...'); - if (!this._config.enabled) { - this._config.log.info(`Configuration is disabled. ${queueNotProcessed}`); + log.info('Processing queue...'); + if (!config.enabled) { + log.info(`Configuration is disabled. ${queueNotProcessed}`); return; } - if (!this._config.isValid) { - this._config.log.info(`Invalid Api Key. ${queueNotProcessed}`); + if (!config.isValid) { + log.info(`Invalid Api Key. ${queueNotProcessed}`); return; } this._processingQueue = true; try { - var events = this._config.storage.get(this.queuePath(), this._config.submissionBatchSize); + var events = config.storage.get(this.queuePath(), config.submissionBatchSize); if (!events || events.length == 0) { this._processingQueue = false; return; } - this._config.log.info(`Sending ${events.length} events to ${this._config.serverUrl}.`); - this._config.submissionClient.postEvents(events, this._config, (response:SubmissionResponse) => { + log.info(`Sending ${events.length} events to ${config.serverUrl}.`); + config.submissionClient.postEvents(events, config, (response:SubmissionResponse) => { this.processSubmissionResponse(response, events); - this._config.log.info('Finished processing queue.'); + log.info('Finished processing queue.'); this._processingQueue = false; }); } catch (ex) { - this._config.log.error(`Error processing queue: ${ex}`); + log.error(`Error processing queue: ${ex}`); this.suspendProcessing(); this._processingQueue = false; } } private processSubmissionResponse(response:SubmissionResponse, events:IEvent[]): void { - var noSubmission = 'The event will not be submitted.'; + const noSubmission:string = 'The event will not be submitted.'; // Optmization for minifier. + var config:Configuration = this._config; // Optmization for minifier. + var log:ILog = config.log; // Optmization for minifier. + if (response.success) { - this._config.log.info(`Sent ${events.length} events.`); + log.info(`Sent ${events.length} events.`); return; } if (response.serviceUnavailable) { // You are currently over your rate limit or the servers are under stress. - this._config.log.error('Server returned service unavailable.'); + log.error('Server returned service unavailable.'); this.suspendProcessing(); this.requeueEvents(events); return; @@ -86,40 +123,40 @@ export class DefaultEventQueue implements IEventQueue { if (response.paymentRequired) { // If the organization over the rate limit then discard the event. - this._config.log.info('Too many events have been submitted, please upgrade your plan.'); + log.info('Too many events have been submitted, please upgrade your plan.'); this.suspendProcessing(null, true, true); return; } if (response.unableToAuthenticate) { // The api key was suspended or could not be authorized. - this._config.log.info(`Unable to authenticate, please check your configuration. ${noSubmission}`); + log.info(`Unable to authenticate, please check your configuration. ${noSubmission}`); this.suspendProcessing(15); return; } if (response.notFound || response.badRequest) { // The service end point could not be found. - this._config.log.error(`Error while trying to submit data: ${response.message}`); + log.error(`Error while trying to submit data: ${response.message}`); this.suspendProcessing(60 * 4); return; } if (response.requestEntityTooLarge) { var message = 'Event submission discarded for being too large.'; - if (this._config.submissionBatchSize > 1) { - this._config.log.error(`${message} Retrying with smaller batch size.`); - this._config.submissionBatchSize = Math.max(1, Math.round(this._config.submissionBatchSize / 1.5)); + if (config.submissionBatchSize > 1) { + log.error(`${message} Retrying with smaller batch size.`); + config.submissionBatchSize = Math.max(1, Math.round(config.submissionBatchSize / 1.5)); this.requeueEvents(events); } else { - this._config.log.error(`${message} ${noSubmission}`); + log.error(`${message} ${noSubmission}`); } return; } if (!response.success) { - this._config.log.error(`Error submitting events: ${ response.message}`); + log.error(`Error submitting events: ${ response.message}`); this.suspendProcessing(); this.requeueEvents(events); } @@ -138,11 +175,13 @@ export class DefaultEventQueue implements IEventQueue { } public suspendProcessing(durationInMinutes?:number, discardFutureQueuedItems?:boolean, clearQueue?:boolean): void { + var config:Configuration = this._config; // Optmization for minifier. + if (!durationInMinutes || durationInMinutes <= 0) { durationInMinutes = 5; } - this._config.log.info(`Suspending processing for ${durationInMinutes} minutes.`); + config.log.info(`Suspending processing for ${durationInMinutes} minutes.`); this._suspendProcessingUntil = new Date(new Date().getTime() + (durationInMinutes * 60000)); if (discardFutureQueuedItems) { @@ -155,7 +194,7 @@ export class DefaultEventQueue implements IEventQueue { // Account is over the limit and we want to ensure that the sample size being sent in will contain newer errors. try { - this._config.storage.clear(this.queuePath()); + config.storage.clear(this.queuePath()); } catch (Exception) { } } diff --git a/src/services/DefaultErrorParser.ts b/src/services/DefaultErrorParser.ts new file mode 100644 index 00000000..129275dd --- /dev/null +++ b/src/services/DefaultErrorParser.ts @@ -0,0 +1,53 @@ +import { IError } from '../models/IError'; +import { IParameter } from '../models/IParameter'; +import { IErrorParser } from 'IErrorParser'; +import { IStackFrame } from '../models/IStackFrame'; +import { EventPluginContext } from '../plugins/EventPluginContext'; + +export class DefaultErrorParser implements IErrorParser { + public parse(context:EventPluginContext, exception:Error): IError { + function getParameters(parameters:string|string[]): IParameter[] { + var params:string[] = (typeof parameters === 'string' ? [parameters] : parameters) || []; + + var result:IParameter[] = []; + for (var index = 0; index < params.length; index++) { + result.push({ name: params[index] }) + } + + return result; + } + + function getStackFrames(context:EventPluginContext, stackFrames:TraceKit.StackFrame[]): IStackFrame[] { + var frames:IStackFrame[] = []; + + for (var index = 0; index < stackFrames.length; index++) { + var frame = stackFrames[index]; + frames.push({ + name: frame.func, + parameters: getParameters(frame.args), + file_name: frame.url, + line_number: frame.line, + column: frame.column + }); + } + + return frames; + } + + const traceKitStackTrace:string = '@@_TraceKit.StackTrace'; // optimization for minifier. + + var stackTrace:TraceKit.StackTrace = !!context.contextData[traceKitStackTrace] + ? context.contextData[traceKitStackTrace] + : TraceKit.computeStackTrace(exception, 25); + + if (!stackTrace) { + throw new Error('Unable to parse the exceptions stack trace.'); + } + + return { + type: stackTrace.name, + message: stackTrace.message || exception.message, + stack_trace: getStackFrames(context, stackTrace.stack || []) + }; + } +} diff --git a/src/services/WebModuleCollector.ts b/src/services/DefaultModuleCollector.ts similarity index 93% rename from src/services/WebModuleCollector.ts rename to src/services/DefaultModuleCollector.ts index 3c5d9b28..1d175c25 100644 --- a/src/services/WebModuleCollector.ts +++ b/src/services/DefaultModuleCollector.ts @@ -3,7 +3,7 @@ import { IModuleCollector } from 'IModuleCollector'; import { EventPluginContext } from '../plugins/EventPluginContext'; import { Utils } from '../Utils'; -export class WebModuleCollector implements IModuleCollector { +export class DefaultModuleCollector implements IModuleCollector { public getModules(context:EventPluginContext): IModule[] { if (document && document.getElementsByTagName) { return null; diff --git a/src/services/WebRequestInfoCollector.ts b/src/services/DefaultRequestInfoCollector.ts similarity index 81% rename from src/services/WebRequestInfoCollector.ts rename to src/services/DefaultRequestInfoCollector.ts index 70023beb..d2b22b82 100644 --- a/src/services/WebRequestInfoCollector.ts +++ b/src/services/DefaultRequestInfoCollector.ts @@ -3,13 +3,13 @@ import { IRequestInfoCollector } from 'IRequestInfoCollector'; import { EventPluginContext } from '../plugins/EventPluginContext'; import { Utils } from '../Utils'; -export class WebRequestInfoCollector implements IRequestInfoCollector { +export class DefaultRequestInfoCollector implements IRequestInfoCollector { public getRequestInfo(context:EventPluginContext): IRequestInfo { if (!document || !navigator || !location) { return null; } - var ri:IRequestInfo = { + var requestInfo:IRequestInfo = { user_agent: navigator.userAgent, is_secure: location.protocol === 'https:', host: location.hostname, @@ -21,9 +21,9 @@ export class WebRequestInfoCollector implements IRequestInfoCollector { }; if (document.referrer && document.referrer !== '') { - ri.referrer = document.referrer; + requestInfo.referrer = document.referrer; } - return ri; + return requestInfo; } } diff --git a/src/services/IErrorParser.ts b/src/services/IErrorParser.ts index b307bca3..e3b52f5c 100644 --- a/src/services/IErrorParser.ts +++ b/src/services/IErrorParser.ts @@ -2,5 +2,5 @@ import { IError } from '../models/IError'; import { EventPluginContext } from '../plugins/EventPluginContext'; export interface IErrorParser { - parse(context:EventPluginContext, exception:Error): void; + parse(context:EventPluginContext, exception:Error): IError; } diff --git a/src/services/NodeEnvironmentInfoCollector.ts b/src/services/NodeEnvironmentInfoCollector.ts index 6ee42939..20caf836 100644 --- a/src/services/NodeEnvironmentInfoCollector.ts +++ b/src/services/NodeEnvironmentInfoCollector.ts @@ -6,6 +6,20 @@ import os = require('os'); export class NodeEnvironmentInfoCollector implements IEnvironmentInfoCollector { public getEnvironmentInfo(context:EventPluginContext): IEnvironmentInfo { + function getIpAddresses():string { + var ips:string[] = []; + var interfaces = os.networkInterfaces(); + Object.keys(interfaces).forEach((name) => { + interfaces[name].forEach((iface:any) => { + if ('IPv4' === iface.family && !iface.internal) { + ips.push(iface.address); + } + }); + }); + + return ips.join(', '); + } + if (!os) { return null; } @@ -22,7 +36,7 @@ export class NodeEnvironmentInfoCollector implements IEnvironmentInfoCollector { architecture: os.arch(), o_s_name: os.type(), o_s_version: os.release(), - ip_address: this.getIpAddresses(), + ip_address: getIpAddresses(), machine_name: os.hostname(), //install_id: '', runtime_version: process.version, @@ -40,18 +54,4 @@ export class NodeEnvironmentInfoCollector implements IEnvironmentInfoCollector { return environmentInfo; } - - private getIpAddresses():string { - var ips:string[] = []; - var interfaces = os.networkInterfaces(); - Object.keys(interfaces).forEach((name) => { - interfaces[name].forEach((iface:any) => { - if ('IPv4' === iface.family && !iface.internal) { - ips.push(iface.address); - } - }); - }); - - return ips.join(', '); - } } diff --git a/src/services/NodeErrorParser.ts b/src/services/NodeErrorParser.ts index ce79dfe7..14f53fbd 100644 --- a/src/services/NodeErrorParser.ts +++ b/src/services/NodeErrorParser.ts @@ -6,39 +6,37 @@ import { EventPluginContext } from '../plugins/EventPluginContext'; import nodestacktrace = require('stack-trace'); export class NodeErrorParser implements IErrorParser { - public parse(context:EventPluginContext, exception:Error): void { + public parse(context:EventPluginContext, exception:Error): IError { + function getStackFrames(context:EventPluginContext, stackFrames:any[]): IStackFrame[] { + var frames:IStackFrame[] = []; + + for (var index = 0; index < stackFrames.length; index++) { + var frame = stackFrames[index]; + frames.push({ + name: frame.getMethodName() || frame.getFunctionName(), + //parameters: frame.args, + file_name: frame.getFileName(), + line_number: frame.getLineNumber(), + column: frame.getColumnNumber(), + declaring_type: frame.getTypeName(), + data: { + is_native: frame.isNative() || (!!frame.filename && frame.filename[0] !== '/' && frame.filename[0] !== '.') + } + }); + } + + return frames; + } + if (!nodestacktrace) { throw new Error('Unable to load the stack trace library.'); } var stackFrames = nodestacktrace.parse(exception) || []; - var error:IError = { + return { type: exception.name, message: exception.message, - stack_trace: this.getStackFrames(context, stackFrames) + stack_trace: getStackFrames(context, stackFrames) }; - - context.event.data['@error'] = error; - } - - private getStackFrames(context:EventPluginContext, stackFrames:any[]): IStackFrame[] { - var frames:IStackFrame[] = []; - - for (var index = 0; index < stackFrames.length; index++) { - var frame = stackFrames[index]; - frames.push({ - name: frame.getMethodName() || frame.getFunctionName(), - //parameters: frame.args, - file_name: frame.getFileName(), - line_number: frame.getLineNumber(), - column: frame.getColumnNumber(), - declaring_type: frame.getTypeName(), - data: { - is_native: frame.isNative() || (!!frame.filename && frame.filename[0] !== '/' && frame.filename[0] !== '.') - } - }); - } - - return frames; } } diff --git a/src/services/NodeRequestInfoCollector.ts b/src/services/NodeRequestInfoCollector.ts index 08d362b9..9204f871 100644 --- a/src/services/NodeRequestInfoCollector.ts +++ b/src/services/NodeRequestInfoCollector.ts @@ -5,12 +5,13 @@ import { Utils } from '../Utils'; export class NodeRequestInfoCollector implements IRequestInfoCollector { getRequestInfo(context:EventPluginContext):IRequestInfo { - if (!context.contextData['@request']) { + const requestKey:string = '@request'; // optimization for minifier. + if (!context.contextData[requestKey]) { return null; } - var request = context.contextData['@request']; - var ri:IRequestInfo = { + var request = context.contextData[requestKey]; + var requestInfo:IRequestInfo = { client_ip_address: request.ip, user_agent: request.headers['user-agent'], is_secure: request.secure, @@ -26,9 +27,9 @@ export class NodeRequestInfoCollector implements IRequestInfoCollector { var host = request.headers['host']; var port:number = host && parseInt(host.slice(host.indexOf(':') + 1)); if (port > 0) { - ri.port = port; + requestInfo.port = port; } - return ri; + return requestInfo; } } diff --git a/src/services/WebErrorParser.ts b/src/services/WebErrorParser.ts deleted file mode 100644 index 8d19c8ae..00000000 --- a/src/services/WebErrorParser.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { IError } from '../models/IError'; -import { IParameter } from '../models/IParameter'; -import { IErrorParser } from 'IErrorParser'; -import { IStackFrame } from '../models/IStackFrame'; -import { EventPluginContext } from '../plugins/EventPluginContext'; - -export class WebErrorParser implements IErrorParser { - public parse(context:EventPluginContext, exception:Error): void { - var stackTrace:TraceKit.StackTrace = !!context.contextData['@@_TraceKit.StackTrace'] - ? context.contextData['@@_TraceKit.StackTrace'] - : TraceKit.computeStackTrace(exception, 25); - - if (!stackTrace) { - throw new Error('Unable to parse the exceptions stack trace.'); - } - - var error:IError = { - type: stackTrace.name, - message: stackTrace.message || exception.message, - stack_trace: this.getStackFrames(context, stackTrace.stack || []) - }; - - context.event.data['@error'] = error; - } - - private getStackFrames(context:EventPluginContext, stackFrames:TraceKit.StackFrame[]): IStackFrame[] { - var frames:IStackFrame[] = []; - - for (var index = 0; index < stackFrames.length; index++) { - var frame = stackFrames[index]; - frames.push({ - name: frame.func, - parameters: this.getParameters(frame.args), - file_name: frame.url, - line_number: frame.line, - column: frame.column - }); - } - - return frames; - } - - private getParameters(parameters:string|string[]): IParameter[] { - var params:string[] = (typeof parameters === 'string' ? [parameters] : parameters) || []; - - var result:IParameter[] = []; - for (var index = 0; index < params.length; index++) { - result.push({ name: params[index] }) - } - - return result; - } -} diff --git a/src/submission/DefaultSubmissionClient.ts b/src/submission/DefaultSubmissionClient.ts index 59d8723c..9597b4c6 100644 --- a/src/submission/DefaultSubmissionClient.ts +++ b/src/submission/DefaultSubmissionClient.ts @@ -1,36 +1,56 @@ import { Configuration } from '../configuration/Configuration'; +import { SettingsManager } from '../configuration/SettingsManager'; import { IEvent } from '../models/IEvent'; +import { IClientConfiguration } from '../models/IClientConfiguration'; import { IUserDescription } from '../models/IUserDescription'; import { ISubmissionClient } from './ISubmissionClient'; import { SettingsResponse } from './SettingsResponse'; -import { SubmissionClientBase } from './SubmissionClientBase'; import { SubmissionResponse } from './SubmissionResponse'; import { Utils } from '../Utils'; declare var XDomainRequest:{ new (); create(); }; -export class DefaultSubmissionClient extends SubmissionClientBase { - private createRequest(config:Configuration, method:string, url:string): XMLHttpRequest { - var xhr:any = new XMLHttpRequest(); - if ('withCredentials' in xhr) { - xhr.open(method, url, true); - } else if (typeof XDomainRequest != 'undefined') { - xhr = new XDomainRequest(); - xhr.open(method, url); - } else { - xhr = null; - } +export class DefaultSubmissionClient implements ISubmissionClient { + public configurationVersionHeader:string = 'X-Exceptionless-ConfigVersion'; + + public postEvents(events:IEvent[], config:Configuration, callback:(response:SubmissionResponse) => void):void { + return this.sendRequest(config, 'POST', '/api/v2/events', Utils.stringify(events, config.dataExclusions), (status:number, message:string, data?:string, headers?:Object) => { + var settingsVersion:number = headers && parseInt(headers[this.configurationVersionHeader]); + SettingsManager.checkVersion(settingsVersion, config); + + callback(new SubmissionResponse(status, message)); + }); + } + + public postUserDescription(referenceId:string, description:IUserDescription, config:Configuration, callback:(response:SubmissionResponse) => void):void { + var path = `/api/v2/events/by-ref/${encodeURIComponent(referenceId)}/user-description`; + return this.sendRequest(config, 'POST', path, Utils.stringify(description, config.dataExclusions), (status:number, message:string, data?:string, headers?:Object) => { + var settingsVersion:number = headers && parseInt(headers[this.configurationVersionHeader]); + SettingsManager.checkVersion(settingsVersion, config); - if (xhr) { - xhr.setRequestHeader('X-Exceptionless-Client', config.userAgent); - if (method === 'POST' && xhr.setRequestHeader) { - xhr.setRequestHeader('Content-Type', 'application/json'); + callback(new SubmissionResponse(status, message)); + }); + } + + public getSettings(config:Configuration, callback:(response:SettingsResponse) => void):void { + return this.sendRequest(config, 'GET', '/api/v2/projects/config', null, (status:number, message:string, data?:string) => { + if (status !== 200) { + return callback(new SettingsResponse(false, null, -1, null, message)); } - xhr.timeout = 10000; - } + var settings:IClientConfiguration; + try { + settings = JSON.parse(data); + } catch (e) { + config.log.error(`Unable to parse settings: '${data}'`); + } - return xhr; + if (!settings || !settings.settings || !settings.version) { + return callback(new SettingsResponse(false, null, -1, null, 'Invalid configuration settings.')); + } + + callback(new SettingsResponse(true, settings.settings, settings.version)); + }); } public sendRequest(config:Configuration, method:string, path:string, data:string, callback: (status:number, message:string, data?:string, headers?:Object) => void): void { @@ -78,8 +98,31 @@ export class DefaultSubmissionClient extends SubmissionClientBase { callback(xhr.status || 500, message, xhr.responseText, parseResponseHeaders(xhr.getAllResponseHeaders())); } + function createRequest(config:Configuration, method:string, url:string): XMLHttpRequest { + var xhr:any = new XMLHttpRequest(); + if ('withCredentials' in xhr) { + xhr.open(method, url, true); + } else if (typeof XDomainRequest != 'undefined') { + xhr = new XDomainRequest(); + xhr.open(method, url); + } else { + xhr = null; + } + + if (xhr) { + xhr.setRequestHeader('X-Exceptionless-Client', config.userAgent); + if (method === 'POST' && xhr.setRequestHeader) { + xhr.setRequestHeader('Content-Type', 'application/json'); + } + + xhr.timeout = 10000; + } + + return xhr; + } + var url = `${config.serverUrl}${path}?access_token=${encodeURIComponent(config.apiKey)}`; - var xhr = this.createRequest(config, method || 'POST', url); + var xhr = createRequest(config, method || 'POST', url); if (!xhr) { return callback(503,'CORS not supported.'); } diff --git a/src/submission/NodeSubmissionClient.ts b/src/submission/NodeSubmissionClient.ts index b806d991..33d215e2 100644 --- a/src/submission/NodeSubmissionClient.ts +++ b/src/submission/NodeSubmissionClient.ts @@ -1,9 +1,8 @@ import { Configuration } from '../configuration/Configuration'; import { IEvent } from '../models/IEvent'; import { IUserDescription } from '../models/IUserDescription'; -import { ISubmissionClient } from './ISubmissionClient'; +import { DefaultSubmissionClient } from './DefaultSubmissionClient'; import { SettingsResponse } from './SettingsResponse'; -import { SubmissionClientBase } from './SubmissionClientBase'; import { SubmissionResponse } from './SubmissionResponse'; import { Utils } from '../Utils'; @@ -11,10 +10,10 @@ import http = require('http'); import https = require('https'); import url = require('url'); -export class NodeSubmissionClient extends SubmissionClientBase { +export class NodeSubmissionClient extends DefaultSubmissionClient { constructor() { super(); - this.configurationVersionHeader = 'x-exceptionless-configversion'; + this.configurationVersionHeader = this.configurationVersionHeader.toLowerCase(); } public sendRequest(config:Configuration, method:string, path:string, data:string, callback: (status:number, message:string, data?:string, headers?:Object) => void): void { diff --git a/src/submission/SubmissionClientBase.ts b/src/submission/SubmissionClientBase.ts deleted file mode 100644 index fedc7c66..00000000 --- a/src/submission/SubmissionClientBase.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Configuration } from '../configuration/Configuration'; -import { SettingsManager } from '../configuration/SettingsManager'; -import { IEvent } from '../models/IEvent'; -import { IClientConfiguration } from '../models/IClientConfiguration'; -import { IUserDescription } from '../models/IUserDescription'; -import { ISubmissionClient } from './ISubmissionClient'; -import { SettingsResponse } from './SettingsResponse'; -import { SubmissionResponse } from './SubmissionResponse'; -import { Utils } from '../Utils'; - -export class SubmissionClientBase implements ISubmissionClient { - public configurationVersionHeader:string = 'X-Exceptionless-ConfigVersion'; - - public postEvents(events:IEvent[], config:Configuration, callback:(response:SubmissionResponse) => void):void { - return this.sendRequest(config, 'POST', '/api/v2/events', Utils.stringify(events, config.dataExclusions), (status:number, message:string, data?:string, headers?:Object) => { - var settingsVersion:number = headers && parseInt(headers[this.configurationVersionHeader]); - SettingsManager.checkVersion(settingsVersion, config); - - callback(new SubmissionResponse(status, message)); - }); - } - - public postUserDescription(referenceId:string, description:IUserDescription, config:Configuration, callback:(response:SubmissionResponse) => void):void { - var path = `/api/v2/events/by-ref/${encodeURIComponent(referenceId)}/user-description`; - return this.sendRequest(config, 'POST', path, Utils.stringify(description, config.dataExclusions), (status:number, message:string, data?:string, headers?:Object) => { - var settingsVersion:number = headers && parseInt(headers[this.configurationVersionHeader]); - SettingsManager.checkVersion(settingsVersion, config); - - callback(new SubmissionResponse(status, message)); - }); - } - - public getSettings(config:Configuration, callback:(response:SettingsResponse) => void):void { - return this.sendRequest(config, 'GET', '/api/v2/projects/config', null, (status:number, message:string, data?:string) => { - if (status !== 200) { - return callback(new SettingsResponse(false, null, -1, null, message)); - } - - var settings:IClientConfiguration; - try { - settings = JSON.parse(data); - } catch (e) { - config.log.error(`Unable to parse settings: '${data}'`); - } - - if (!settings || !settings.settings || !settings.version) { - return callback(new SettingsResponse(false, null, -1, null, 'Invalid configuration settings.')); - } - - callback(new SettingsResponse(true, settings.settings, settings.version)); - }); - } - - public sendRequest(config:Configuration, method:string, path:string, data:string, callback: (status:number, message:string, data?:string, headers?:Object) => void): void { - callback(500, 'Not Implemented'); - } -} From ea56b974f3789c4529ea0c03f623e1e860c6ae2f Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Fri, 22 May 2015 16:05:39 -0500 Subject: [PATCH 2/2] Tested previous optimizations and bumped the version. --- bower.json | 2 +- dist/exceptionless.d.ts | 48 +-- dist/exceptionless.js | 775 ++++++++++++++++------------------ dist/exceptionless.js.map | 2 +- dist/exceptionless.min.js | 4 +- dist/exceptionless.min.js.map | 2 +- package.json | 2 +- 7 files changed, 394 insertions(+), 441 deletions(-) diff --git a/bower.json b/bower.json index a46d846d..a6fe4c29 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "exceptionless", - "version": "0.3.0", + "version": "0.3.1", "description": "JavaScript client for Exceptionless", "license": "Apache", "main": "dist/exceptionless.js", diff --git a/dist/exceptionless.d.ts b/dist/exceptionless.d.ts index 31a17cd9..d6b62e4e 100644 --- a/dist/exceptionless.d.ts +++ b/dist/exceptionless.d.ts @@ -59,7 +59,7 @@ export interface IEnvironmentInfoCollector { getEnvironmentInfo(context: EventPluginContext): IEnvironmentInfo; } export interface IErrorParser { - parse(context: EventPluginContext, exception: Error): void; + parse(context: EventPluginContext, exception: Error): IError; } export interface IModuleCollector { getModules(context: EventPluginContext): IModule[]; @@ -102,7 +102,7 @@ export declare class SettingsManager { static checkVersion(version: number, config: Configuration): void; static updateSettings(config: Configuration): void; } -export declare class InMemoryLastReferenceIdManager implements ILastReferenceIdManager { +export declare class DefaultLastReferenceIdManager implements ILastReferenceIdManager { private _lastReferenceId; getLast(): string; clearLast(): void; @@ -183,13 +183,9 @@ export declare class Utils { static stringify(data: any, exclusions?: string[]): string; } export declare class Configuration implements IConfigurationSettings { - private _apiKey; - private _enabled; - private _serverUrl; - private _dataExclusions; - private _plugins; defaultTags: string[]; defaultData: Object; + enabled: boolean; environmentInfoCollector: IEnvironmentInfoCollector; errorParser: IErrorParser; lastReferenceIdManager: ILastReferenceIdManager; @@ -202,12 +198,15 @@ export declare class Configuration implements IConfigurationSettings { storage: IStorage; queue: IEventQueue; constructor(configSettings?: IConfigurationSettings); + private _apiKey; apiKey: string; isValid: boolean; + private _serverUrl; serverUrl: string; - enabled: boolean; + private _dataExclusions; dataExclusions: string[]; addDataExclusions(...exclusions: string[]): void; + private _plugins; plugins: IEventPlugin[]; addPlugin(plugin: IEventPlugin): void; addPlugin(name: string, priority: number, pluginAction: (context: EventPluginContext, next?: () => void) => void): void; @@ -346,11 +345,6 @@ export declare class ErrorPlugin implements IEventPlugin { name: string; run(context: EventPluginContext, next?: () => void): void; } -export declare class DuplicateCheckerPlugin implements IEventPlugin { - priority: number; - name: string; - run(context: EventPluginContext, next?: () => void): void; -} export declare class ModuleInfoPlugin implements IEventPlugin { priority: number; name: string; @@ -395,11 +389,9 @@ export declare class SettingsResponse { } export declare class NodeEnvironmentInfoCollector implements IEnvironmentInfoCollector { getEnvironmentInfo(context: EventPluginContext): IEnvironmentInfo; - private getIpAddresses(); } export declare class NodeErrorParser implements IErrorParser { - parse(context: EventPluginContext, exception: Error): void; - private getStackFrames(context, stackFrames); + parse(context: EventPluginContext, exception: Error): IError; } export declare class NodeRequestInfoCollector implements IRequestInfoCollector { getRequestInfo(context: EventPluginContext): IRequestInfo; @@ -408,39 +400,29 @@ export interface IClientConfiguration { settings: Object; version: number; } -export declare class SubmissionClientBase implements ISubmissionClient { +export declare class DefaultSubmissionClient implements ISubmissionClient { configurationVersionHeader: string; postEvents(events: IEvent[], config: Configuration, callback: (response: SubmissionResponse) => void): void; postUserDescription(referenceId: string, description: IUserDescription, config: Configuration, callback: (response: SubmissionResponse) => void): void; getSettings(config: Configuration, callback: (response: SettingsResponse) => void): void; sendRequest(config: Configuration, method: string, path: string, data: string, callback: (status: number, message: string, data?: string, headers?: Object) => void): void; } -export declare class NodeSubmissionClient extends SubmissionClientBase { +export declare class NodeSubmissionClient extends DefaultSubmissionClient { constructor(); sendRequest(config: Configuration, method: string, path: string, data: string, callback: (status: number, message: string, data?: string, headers?: Object) => void): void; } export declare class NodeBootstrapper implements IBootstrapper { register(): void; - private getExitCodeReason(code); } -export declare class WebErrorParser implements IErrorParser { - parse(context: EventPluginContext, exception: Error): void; - private getStackFrames(context, stackFrames); - private getParameters(parameters); +export declare class DefaultErrorParser implements IErrorParser { + parse(context: EventPluginContext, exception: Error): IError; } -export declare class WebModuleCollector implements IModuleCollector { +export declare class DefaultModuleCollector implements IModuleCollector { getModules(context: EventPluginContext): IModule[]; } -export declare class WebRequestInfoCollector implements IRequestInfoCollector { +export declare class DefaultRequestInfoCollector implements IRequestInfoCollector { getRequestInfo(context: EventPluginContext): IRequestInfo; } -export declare class DefaultSubmissionClient extends SubmissionClientBase { - private createRequest(config, method, url); - sendRequest(config: Configuration, method: string, path: string, data: string, callback: (status: number, message: string, data?: string, headers?: Object) => void): void; -} -export declare class WindowBootstrapper implements IBootstrapper { +export declare class DefaultBootstrapper implements IBootstrapper { register(): void; - private getDefaultsSettingsFromScriptTag(); - private processUnhandledException(stackTrace, options?); - private processJQueryAjaxError(event, xhr, settings, error); } diff --git a/dist/exceptionless.js b/dist/exceptionless.js index 1674dade..174447b2 100644 --- a/dist/exceptionless.js +++ b/dist/exceptionless.js @@ -1189,7 +1189,7 @@ var ContextData = (function () { configurable: true }); ContextData.prototype.getException = function () { - return this.hasException ? this['@@_Exception'] : null; + return this['@@_Exception'] || null; }; ContextData.prototype.markAsUnhandledError = function () { this['@@_IsUnhandledError'] = true; @@ -1202,12 +1202,12 @@ var ContextData = (function () { configurable: true }); ContextData.prototype.setSubmissionMethod = function (method) { - if (method && method.length > 0) { + if (method) { this['@@_SubmissionMethod'] = method; } }; ContextData.prototype.getSubmissionMethod = function () { - return !!this['@@_SubmissionMethod'] ? this['@@_SubmissionMethod'] : null; + return this['@@_SubmissionMethod'] || null; }; return ContextData; })(); @@ -1216,8 +1216,9 @@ var SettingsManager = (function () { function SettingsManager() { } SettingsManager.changed = function (config) { - for (var index = 0; index < this._handlers.length; index++) { - this._handlers[index](config); + var handlers = this._handlers; + for (var index = 0; index < handlers.length; index++) { + handlers[index](config); } }; SettingsManager.onChanged = function (handler) { @@ -1232,13 +1233,12 @@ var SettingsManager = (function () { return config.storage.get(this._configPath, 1)[0] || {}; }; SettingsManager.checkVersion = function (version, config) { - if (isNaN(version) || version <= 0) { - return; - } - var savedConfigVersion = parseInt(config.storage.get(this._configPath + "-version", 1)[0]); - if (isNaN(savedConfigVersion) || version > savedConfigVersion) { - config.log.info("Updating settings from v" + (!isNaN(savedConfigVersion) ? savedConfigVersion : 0) + " to v" + version); - this.updateSettings(config); + if (version) { + var savedConfigVersion = parseInt(config.storage.get(this._configPath + "-version", 1)[0]); + if (isNaN(savedConfigVersion) || version > savedConfigVersion) { + config.log.info("Updating settings from v" + (!isNaN(savedConfigVersion) ? savedConfigVersion : 0) + " to v" + version); + this.updateSettings(config); + } } }; SettingsManager.updateSettings = function (config) { @@ -1259,8 +1259,9 @@ var SettingsManager = (function () { } delete config.settings[key]; } - config.storage.save(_this._configPath + "-version", response.settingsVersion); - config.storage.save(_this._configPath, response.settings); + var path = SettingsManager._configPath; + config.storage.save(path + "-version", response.settingsVersion); + config.storage.save(path, response.settings); config.log.info('Updated settings'); _this.changed(config); }); @@ -1270,22 +1271,22 @@ var SettingsManager = (function () { return SettingsManager; })(); exports.SettingsManager = SettingsManager; -var InMemoryLastReferenceIdManager = (function () { - function InMemoryLastReferenceIdManager() { +var DefaultLastReferenceIdManager = (function () { + function DefaultLastReferenceIdManager() { this._lastReferenceId = null; } - InMemoryLastReferenceIdManager.prototype.getLast = function () { + DefaultLastReferenceIdManager.prototype.getLast = function () { return this._lastReferenceId; }; - InMemoryLastReferenceIdManager.prototype.clearLast = function () { + DefaultLastReferenceIdManager.prototype.clearLast = function () { this._lastReferenceId = null; }; - InMemoryLastReferenceIdManager.prototype.setLast = function (eventId) { + DefaultLastReferenceIdManager.prototype.setLast = function (eventId) { this._lastReferenceId = eventId; }; - return InMemoryLastReferenceIdManager; + return DefaultLastReferenceIdManager; })(); -exports.InMemoryLastReferenceIdManager = InMemoryLastReferenceIdManager; +exports.DefaultLastReferenceIdManager = DefaultLastReferenceIdManager; var ConsoleLog = (function () { function ConsoleLog() { } @@ -1364,7 +1365,6 @@ var EventPluginManager = (function () { EventPluginManager.addDefaultPlugins = function (config) { config.addPlugin(new ConfigurationDefaultsPlugin()); config.addPlugin(new ErrorPlugin()); - config.addPlugin(new DuplicateCheckerPlugin()); config.addPlugin(new ModuleInfoPlugin()); config.addPlugin(new RequestInfoPlugin()); config.addPlugin(new EnvironmentInfoPlugin()); @@ -1382,9 +1382,7 @@ var ReferenceIdPlugin = (function () { if ((!context.event.reference_id || context.event.reference_id.length === 0) && context.event.type === 'error') { context.event.reference_id = Utils.guid().replace('-', '').substring(0, 10); } - if (next) { - next(); - } + next && next(); }; return ReferenceIdPlugin; })(); @@ -1395,92 +1393,97 @@ var DefaultEventQueue = (function () { this._config = config; } DefaultEventQueue.prototype.enqueue = function (event) { + var config = this._config; this.ensureQueueTimer(); if (this.areQueuedItemsDiscarded()) { - this._config.log.info('Queue items are currently being discarded. The event will not be queued.'); + config.log.info('Queue items are currently being discarded. The event will not be queued.'); return; } var key = this.queuePath() + "-" + new Date().toJSON() + "-" + Utils.randomNumber(); - this._config.log.info("Enqueuing event: " + key + " type=" + event.type + " " + (!!event.reference_id ? 'refid=' + event.reference_id : '')); - this._config.storage.save(key, event); + config.log.info("Enqueuing event: " + key + " type=" + event.type + " " + (!!event.reference_id ? 'refid=' + event.reference_id : '')); + config.storage.save(key, event); }; DefaultEventQueue.prototype.process = function () { var _this = this; + var queueNotProcessed = 'The queue will not be processed.'; + var config = this._config; + var log = config.log; this.ensureQueueTimer(); if (this._processingQueue) { return; } - var queueNotProcessed = 'The queue will not be processed.'; - this._config.log.info('Processing queue...'); - if (!this._config.enabled) { - this._config.log.info("Configuration is disabled. " + queueNotProcessed); + log.info('Processing queue...'); + if (!config.enabled) { + log.info("Configuration is disabled. " + queueNotProcessed); return; } - if (!this._config.isValid) { - this._config.log.info("Invalid Api Key. " + queueNotProcessed); + if (!config.isValid) { + log.info("Invalid Api Key. " + queueNotProcessed); return; } this._processingQueue = true; try { - var events = this._config.storage.get(this.queuePath(), this._config.submissionBatchSize); + var events = config.storage.get(this.queuePath(), config.submissionBatchSize); if (!events || events.length == 0) { this._processingQueue = false; return; } - this._config.log.info("Sending " + events.length + " events to " + this._config.serverUrl + "."); - this._config.submissionClient.postEvents(events, this._config, function (response) { + log.info("Sending " + events.length + " events to " + config.serverUrl + "."); + config.submissionClient.postEvents(events, config, function (response) { _this.processSubmissionResponse(response, events); - _this._config.log.info('Finished processing queue.'); + log.info('Finished processing queue.'); _this._processingQueue = false; }); } catch (ex) { - this._config.log.error("Error processing queue: " + ex); + log.error("Error processing queue: " + ex); this.suspendProcessing(); this._processingQueue = false; } }; DefaultEventQueue.prototype.processSubmissionResponse = function (response, events) { var noSubmission = 'The event will not be submitted.'; + var config = this._config; + var log = config.log; if (response.success) { - this._config.log.info("Sent " + events.length + " events."); + log.info("Sent " + events.length + " events."); return; } if (response.serviceUnavailable) { - this._config.log.error('Server returned service unavailable.'); + log.error('Server returned service unavailable.'); this.suspendProcessing(); this.requeueEvents(events); return; } if (response.paymentRequired) { - this._config.log.info('Too many events have been submitted, please upgrade your plan.'); + log.info('Too many events have been submitted, please upgrade your plan.'); this.suspendProcessing(null, true, true); return; } if (response.unableToAuthenticate) { - this._config.log.info("Unable to authenticate, please check your configuration. " + noSubmission); + log.info("Unable to authenticate, please check your configuration. " + noSubmission); this.suspendProcessing(15); return; } if (response.notFound || response.badRequest) { - this._config.log.error("Error while trying to submit data: " + response.message); + log.error("Error while trying to submit data: " + response.message); this.suspendProcessing(60 * 4); return; } if (response.requestEntityTooLarge) { var message = 'Event submission discarded for being too large.'; - if (this._config.submissionBatchSize > 1) { - this._config.log.error(message + " Retrying with smaller batch size."); - this._config.submissionBatchSize = Math.max(1, Math.round(this._config.submissionBatchSize / 1.5)); + if (config.submissionBatchSize > 1) { + log.error(message + " Retrying with smaller batch size."); + config.submissionBatchSize = Math.max(1, Math.round(config.submissionBatchSize / 1.5)); this.requeueEvents(events); } else { - this._config.log.error(message + " " + noSubmission); + log.error(message + " " + noSubmission); } return; } if (!response.success) { - this._config.log.error("Error submitting events: " + response.message); + log.error("Error submitting events: " + response.message); this.suspendProcessing(); this.requeueEvents(events); } @@ -1497,10 +1500,11 @@ var DefaultEventQueue = (function () { } }; DefaultEventQueue.prototype.suspendProcessing = function (durationInMinutes, discardFutureQueuedItems, clearQueue) { + var config = this._config; if (!durationInMinutes || durationInMinutes <= 0) { durationInMinutes = 5; } - this._config.log.info("Suspending processing for " + durationInMinutes + " minutes."); + config.log.info("Suspending processing for " + durationInMinutes + " minutes."); this._suspendProcessingUntil = new Date(new Date().getTime() + (durationInMinutes * 60000)); if (discardFutureQueuedItems) { this._discardQueuedItemsUntil = new Date(new Date().getTime() + (durationInMinutes * 60000)); @@ -1509,7 +1513,7 @@ var DefaultEventQueue = (function () { return; } try { - this._config.storage.clear(this.queuePath()); + config.storage.clear(this.queuePath()); } catch (Exception) { } }; @@ -1727,14 +1731,14 @@ var Utils = (function () { exports.Utils = Utils; var Configuration = (function () { function Configuration(configSettings) { - this._enabled = true; - this._serverUrl = 'https://collector.exceptionless.io'; - this._dataExclusions = []; - this._plugins = []; this.defaultTags = []; this.defaultData = {}; - this.lastReferenceIdManager = new InMemoryLastReferenceIdManager(); + this.enabled = true; + this.lastReferenceIdManager = new DefaultLastReferenceIdManager(); this.settings = {}; + this._serverUrl = 'https://collector.exceptionless.io'; + this._dataExclusions = []; + this._plugins = []; function inject(fn) { return typeof fn === 'function' ? fn(this) : fn; } @@ -1744,7 +1748,7 @@ var Configuration = (function () { this.serverUrl = configSettings.serverUrl; this.environmentInfoCollector = inject(configSettings.environmentInfoCollector); this.errorParser = inject(configSettings.errorParser); - this.lastReferenceIdManager = inject(configSettings.lastReferenceIdManager) || new InMemoryLastReferenceIdManager(); + this.lastReferenceIdManager = inject(configSettings.lastReferenceIdManager) || new DefaultLastReferenceIdManager(); this.moduleCollector = inject(configSettings.moduleCollector); this.requestInfoCollector = inject(configSettings.requestInfoCollector); this.submissionBatchSize = inject(configSettings.submissionBatchSize) || 50; @@ -1777,7 +1781,7 @@ var Configuration = (function () { return this._serverUrl; }, set: function (value) { - if (!!value && value.length > 0) { + if (!!value) { this._serverUrl = value; this.log.info("serverUrl: " + this._serverUrl); } @@ -1785,13 +1789,6 @@ var Configuration = (function () { enumerable: true, configurable: true }); - Object.defineProperty(Configuration.prototype, "enabled", { - get: function () { - return this._enabled; - }, - enumerable: true, - configurable: true - }); Object.defineProperty(Configuration.prototype, "dataExclusions", { get: function () { var exclusions = this.settings['@@DataExclusions']; @@ -1819,7 +1816,7 @@ var Configuration = (function () { Configuration.prototype.addPlugin = function (pluginOrName, priority, pluginAction) { var plugin = !!pluginAction ? { name: pluginOrName, priority: priority, run: pluginAction } : pluginOrName; if (!plugin || !plugin.run) { - this.log.error('Add plugin failed: No run method was defined.'); + this.log.error('Add plugin failed: Run method not defined'); return; } if (!plugin.name) { @@ -1829,48 +1826,51 @@ var Configuration = (function () { plugin.priority = 0; } var pluginExists = false; - for (var index = 0; index < this._plugins.length; index++) { - if (this._plugins[index].name === plugin.name) { + var plugins = this._plugins; + for (var index = 0; index < plugins.length; index++) { + if (plugins[index].name === plugin.name) { pluginExists = true; break; } } if (!pluginExists) { - this._plugins.push(plugin); + plugins.push(plugin); } }; Configuration.prototype.removePlugin = function (pluginOrName) { var name = typeof pluginOrName === 'string' ? pluginOrName : pluginOrName.name; if (!name) { - this.log.error('Remove plugin failed: No plugin name was defined.'); + this.log.error('Remove plugin failed: Plugin name not defined'); return; } - for (var index = 0; index < this._plugins.length; index++) { - if (this._plugins[index].name === name) { - this._plugins.splice(index, 1); + var plugins = this._plugins; + for (var index = 0; index < plugins.length; index++) { + if (plugins[index].name === name) { + plugins.splice(index, 1); break; } } }; Configuration.prototype.setVersion = function (version) { - if (!!version && version.length > 0) { + if (!!version) { this.defaultData['@version'] = version; } }; Configuration.prototype.setUserIdentity = function (userInfoOrIdentity, name) { + var user = '@user'; var userInfo = typeof userInfoOrIdentity !== 'string' ? userInfoOrIdentity : { identity: userInfoOrIdentity, name: name }; var shouldRemove = !userInfo || (!userInfo.identity && !userInfo.name); if (shouldRemove) { - delete this.defaultData['@user']; + delete this.defaultData[user]; } else { - this.defaultData['@user'] = userInfo; + this.defaultData[user] = userInfo; } this.log.info("user identity: " + (shouldRemove ? 'null' : userInfo.identity)); }; Object.defineProperty(Configuration.prototype, "userAgent", { get: function () { - return 'exceptionless-js/0.3.0'; + return 'exceptionless-js/0.3.1'; }, enumerable: true, configurable: true @@ -1903,13 +1903,13 @@ var EventBuilder = (function () { this.pluginContextData = pluginContextData || new ContextData(); } EventBuilder.prototype.setType = function (type) { - if (!!type && type.length > 0) { + if (!!type) { this.target.type = type; } return this; }; EventBuilder.prototype.setSource = function (source) { - if (!!source && source.length > 0) { + if (!!source) { this.target.source = source; } return this; @@ -1929,7 +1929,7 @@ var EventBuilder = (function () { return this; }; EventBuilder.prototype.setMessage = function (message) { - if (!!message && message.length > 0) { + if (!!message) { this.target.message = message; } return this; @@ -1990,7 +1990,7 @@ var EventBuilder = (function () { this.client.submitEvent(this.target, this.pluginContextData, callback); }; EventBuilder.prototype.isValidIdentifier = function (value) { - if (!value || !value.length) { + if (!value) { return true; } if (value.length < 8 || value.length > 100) { @@ -2034,8 +2034,6 @@ var SubmissionResponse = (function () { exports.SubmissionResponse = SubmissionResponse; var ExceptionlessClient = (function () { function ExceptionlessClient(settingsOrApiKey, serverUrl) { - // TODO: populate this in a plugin.. - //var settings = this.getSettingsFromScriptTag() || {}; if (typeof settingsOrApiKey !== 'object') { this.config = new Configuration(settingsOrApiKey); } @@ -2193,9 +2191,7 @@ var ConfigurationDefaultsPlugin = (function () { context.event.data[key] = defaultData[key]; } } - if (next) { - next(); - } + next && next(); }; return ConfigurationDefaultsPlugin; })(); @@ -2206,53 +2202,41 @@ var ErrorPlugin = (function () { this.name = 'ErrorPlugin'; } ErrorPlugin.prototype.run = function (context, next) { + var error = '@error'; var exception = context.contextData.getException(); if (!!exception) { context.event.type = 'error'; - if (!context.event.data['@error']) { + if (!context.event.data[error]) { var parser = context.client.config.errorParser; if (!parser) { throw new Error('No error parser was defined.'); } - parser.parse(context, exception); + var result = parser.parse(context, exception); + if (!!result) { + context.event.data[error] = result; + } } } - if (next) { - next(); - } + next && next(); }; return ErrorPlugin; })(); exports.ErrorPlugin = ErrorPlugin; -var DuplicateCheckerPlugin = (function () { - function DuplicateCheckerPlugin() { - this.priority = 50; - this.name = 'DuplicateCheckerPlugin'; - } - DuplicateCheckerPlugin.prototype.run = function (context, next) { - if (next) { - next(); - } - }; - return DuplicateCheckerPlugin; -})(); -exports.DuplicateCheckerPlugin = DuplicateCheckerPlugin; var ModuleInfoPlugin = (function () { function ModuleInfoPlugin() { this.priority = 40; this.name = 'ModuleInfoPlugin'; } ModuleInfoPlugin.prototype.run = function (context, next) { - var moduleCollector = context.client.config.moduleCollector; - if (context.event.data['@error'] && !context.event.data['@error'].modules && !!moduleCollector) { - var modules = moduleCollector.getModules(context); + var error = '@error'; + var collector = context.client.config.moduleCollector; + if (context.event.data[error] && !context.event.data['@error'].modules && !!collector) { + var modules = collector.getModules(context); if (modules && modules.length > 0) { - context.event.data['@error'].modules = modules; + context.event.data[error].modules = modules; } } - if (next) { - next(); - } + next && next(); }; return ModuleInfoPlugin; })(); @@ -2263,16 +2247,15 @@ var RequestInfoPlugin = (function () { this.name = 'RequestInfoPlugin'; } RequestInfoPlugin.prototype.run = function (context, next) { - var requestInfoCollector = context.client.config.requestInfoCollector; - if (!context.event.data['@request'] && !!requestInfoCollector) { - var ri = requestInfoCollector.getRequestInfo(context); - if (!!ri) { - context.event.data['@request'] = ri; + var request = '@request'; + var collector = context.client.config.requestInfoCollector; + if (!context.event.data[request] && !!collector) { + var requestInfo = collector.getRequestInfo(context); + if (!!requestInfo) { + context.event.data[request] = requestInfo; } } - if (next) { - next(); - } + next && next(); }; return RequestInfoPlugin; })(); @@ -2283,15 +2266,15 @@ var EnvironmentInfoPlugin = (function () { this.name = 'EnvironmentInfoPlugin'; } EnvironmentInfoPlugin.prototype.run = function (context, next) { - if (!context.event.data['@environment'] && context.client.config.environmentInfoCollector) { - var ei = context.client.config.environmentInfoCollector.getEnvironmentInfo(context); - if (!!ei) { - context.event.data['@environment'] = ei; + var environment = '@environment'; + var collector = context.client.config.environmentInfoCollector; + if (!context.event.data[environment] && collector) { + var environmentInfo = collector.getEnvironmentInfo(context); + if (!!environmentInfo) { + context.event.data[environment] = environmentInfo; } } - if (next) { - next(); - } + next && next(); }; return EnvironmentInfoPlugin; })(); @@ -2306,9 +2289,7 @@ var SubmissionMethodPlugin = (function () { if (!!submissionMethod) { context.event.data['@submission_method'] = submissionMethod; } - if (next) { - next(); - } + next && next(); }; return SubmissionMethodPlugin; })(); @@ -2334,6 +2315,18 @@ var NodeEnvironmentInfoCollector = (function () { function NodeEnvironmentInfoCollector() { } NodeEnvironmentInfoCollector.prototype.getEnvironmentInfo = function (context) { + function getIpAddresses() { + var ips = []; + var interfaces = os.networkInterfaces(); + Object.keys(interfaces).forEach(function (name) { + interfaces[name].forEach(function (iface) { + if ('IPv4' === iface.family && !iface.internal) { + ips.push(iface.address); + } + }); + }); + return ips.join(', '); + } if (!os) { return null; } @@ -2348,7 +2341,7 @@ var NodeEnvironmentInfoCollector = (function () { architecture: os.arch(), o_s_name: os.type(), o_s_version: os.release(), - ip_address: this.getIpAddresses(), + ip_address: getIpAddresses(), machine_name: os.hostname(), runtime_version: process.version, data: { @@ -2363,18 +2356,6 @@ var NodeEnvironmentInfoCollector = (function () { } return environmentInfo; }; - NodeEnvironmentInfoCollector.prototype.getIpAddresses = function () { - var ips = []; - var interfaces = os.networkInterfaces(); - Object.keys(interfaces).forEach(function (name) { - interfaces[name].forEach(function (iface) { - if ('IPv4' === iface.family && !iface.internal) { - ips.push(iface.address); - } - }); - }); - return ips.join(', '); - }; return NodeEnvironmentInfoCollector; })(); exports.NodeEnvironmentInfoCollector = NodeEnvironmentInfoCollector; @@ -2383,33 +2364,32 @@ var NodeErrorParser = (function () { function NodeErrorParser() { } NodeErrorParser.prototype.parse = function (context, exception) { + function getStackFrames(context, stackFrames) { + var frames = []; + for (var index = 0; index < stackFrames.length; index++) { + var frame = stackFrames[index]; + frames.push({ + name: frame.getMethodName() || frame.getFunctionName(), + file_name: frame.getFileName(), + line_number: frame.getLineNumber(), + column: frame.getColumnNumber(), + declaring_type: frame.getTypeName(), + data: { + is_native: frame.isNative() || (!!frame.filename && frame.filename[0] !== '/' && frame.filename[0] !== '.') + } + }); + } + return frames; + } if (!nodestacktrace) { throw new Error('Unable to load the stack trace library.'); } var stackFrames = nodestacktrace.parse(exception) || []; - var error = { + return { type: exception.name, message: exception.message, - stack_trace: this.getStackFrames(context, stackFrames) + stack_trace: getStackFrames(context, stackFrames) }; - context.event.data['@error'] = error; - }; - NodeErrorParser.prototype.getStackFrames = function (context, stackFrames) { - var frames = []; - for (var index = 0; index < stackFrames.length; index++) { - var frame = stackFrames[index]; - frames.push({ - name: frame.getMethodName() || frame.getFunctionName(), - file_name: frame.getFileName(), - line_number: frame.getLineNumber(), - column: frame.getColumnNumber(), - declaring_type: frame.getTypeName(), - data: { - is_native: frame.isNative() || (!!frame.filename && frame.filename[0] !== '/' && frame.filename[0] !== '.') - } - }); - } - return frames; }; return NodeErrorParser; })(); @@ -2418,11 +2398,12 @@ var NodeRequestInfoCollector = (function () { function NodeRequestInfoCollector() { } NodeRequestInfoCollector.prototype.getRequestInfo = function (context) { - if (!context.contextData['@request']) { + var requestKey = '@request'; + if (!context.contextData[requestKey]) { return null; } - var request = context.contextData['@request']; - var ri = { + var request = context.contextData[requestKey]; + var requestInfo = { client_ip_address: request.ip, user_agent: request.headers['user-agent'], is_secure: request.secure, @@ -2436,18 +2417,18 @@ var NodeRequestInfoCollector = (function () { var host = request.headers['host']; var port = host && parseInt(host.slice(host.indexOf(':') + 1)); if (port > 0) { - ri.port = port; + requestInfo.port = port; } - return ri; + return requestInfo; }; return NodeRequestInfoCollector; })(); exports.NodeRequestInfoCollector = NodeRequestInfoCollector; -var SubmissionClientBase = (function () { - function SubmissionClientBase() { +var DefaultSubmissionClient = (function () { + function DefaultSubmissionClient() { this.configurationVersionHeader = 'X-Exceptionless-ConfigVersion'; } - SubmissionClientBase.prototype.postEvents = function (events, config, callback) { + DefaultSubmissionClient.prototype.postEvents = function (events, config, callback) { var _this = this; return this.sendRequest(config, 'POST', '/api/v2/events', Utils.stringify(events, config.dataExclusions), function (status, message, data, headers) { var settingsVersion = headers && parseInt(headers[_this.configurationVersionHeader]); @@ -2455,7 +2436,7 @@ var SubmissionClientBase = (function () { callback(new SubmissionResponse(status, message)); }); }; - SubmissionClientBase.prototype.postUserDescription = function (referenceId, description, config, callback) { + DefaultSubmissionClient.prototype.postUserDescription = function (referenceId, description, config, callback) { var _this = this; var path = "/api/v2/events/by-ref/" + encodeURIComponent(referenceId) + "/user-description"; return this.sendRequest(config, 'POST', path, Utils.stringify(description, config.dataExclusions), function (status, message, data, headers) { @@ -2464,7 +2445,7 @@ var SubmissionClientBase = (function () { callback(new SubmissionResponse(status, message)); }); }; - SubmissionClientBase.prototype.getSettings = function (config, callback) { + DefaultSubmissionClient.prototype.getSettings = function (config, callback) { return this.sendRequest(config, 'GET', '/api/v2/projects/config', null, function (status, message, data) { if (status !== 200) { return callback(new SettingsResponse(false, null, -1, null, message)); @@ -2482,12 +2463,91 @@ var SubmissionClientBase = (function () { callback(new SettingsResponse(true, settings.settings, settings.version)); }); }; - SubmissionClientBase.prototype.sendRequest = function (config, method, path, data, callback) { - callback(500, 'Not Implemented'); + DefaultSubmissionClient.prototype.sendRequest = function (config, method, path, data, callback) { + var isCompleted = false; + function complete(xhr) { + function parseResponseHeaders(headerStr) { + var headers = {}; + var headerPairs = (headerStr || '').split('\u000d\u000a'); + for (var index = 0; index < headerPairs.length; index++) { + var headerPair = headerPairs[index]; + var separator = headerPair.indexOf('\u003a\u0020'); + if (separator > 0) { + headers[headerPair.substring(0, separator)] = headerPair.substring(separator + 2); + } + } + return headers; + } + if (isCompleted) { + return; + } + else { + isCompleted = true; + } + var message; + if (xhr.status === 0) { + message = 'Unable to connect to server.'; + } + else if (xhr.status < 200 || xhr.status > 299) { + if (!!xhr.responseBody && !!xhr.responseBody.message) { + message = xhr.responseBody.message; + } + else if (!!xhr.responseText && xhr.responseText.indexOf('message') !== -1) { + try { + message = JSON.parse(xhr.responseText).message; + } + catch (e) { + message = xhr.responseText; + } + } + else { + message = xhr.statusText; + } + } + callback(xhr.status || 500, message, xhr.responseText, parseResponseHeaders(xhr.getAllResponseHeaders())); + } + function createRequest(config, method, url) { + var xhr = new XMLHttpRequest(); + if ('withCredentials' in xhr) { + xhr.open(method, url, true); + } + else if (typeof XDomainRequest != 'undefined') { + xhr = new XDomainRequest(); + xhr.open(method, url); + } + else { + xhr = null; + } + if (xhr) { + xhr.setRequestHeader('X-Exceptionless-Client', config.userAgent); + if (method === 'POST' && xhr.setRequestHeader) { + xhr.setRequestHeader('Content-Type', 'application/json'); + } + xhr.timeout = 10000; + } + return xhr; + } + var url = "" + config.serverUrl + path + "?access_token=" + encodeURIComponent(config.apiKey); + var xhr = createRequest(config, method || 'POST', url); + if (!xhr) { + return callback(503, 'CORS not supported.'); + } + if ('withCredentials' in xhr) { + xhr.onreadystatechange = function () { + if (xhr.readyState !== 4) { + return; + } + complete(xhr); + }; + } + xhr.ontimeout = function () { return complete(xhr); }; + xhr.onerror = function () { return complete(xhr); }; + xhr.onload = function () { return complete(xhr); }; + xhr.send(data); }; - return SubmissionClientBase; + return DefaultSubmissionClient; })(); -exports.SubmissionClientBase = SubmissionClientBase; +exports.DefaultSubmissionClient = DefaultSubmissionClient; var http = require('http'); var https = require('https'); var url = require('url'); @@ -2495,7 +2555,7 @@ var NodeSubmissionClient = (function (_super) { __extends(NodeSubmissionClient, _super); function NodeSubmissionClient() { _super.call(this); - this.configurationVersionHeader = 'x-exceptionless-configversion'; + this.configurationVersionHeader = this.configurationVersionHeader.toLowerCase(); } NodeSubmissionClient.prototype.sendRequest = function (config, method, path, data, callback) { function complete(response, data, headers) { @@ -2538,118 +2598,119 @@ var NodeSubmissionClient = (function (_super) { request.end(); }; return NodeSubmissionClient; -})(SubmissionClientBase); +})(DefaultSubmissionClient); exports.NodeSubmissionClient = NodeSubmissionClient; var NodeBootstrapper = (function () { function NodeBootstrapper() { } NodeBootstrapper.prototype.register = function () { - var _this = this; + var beforeExit = 'beforeExit'; + var uncaughtException = 'uncaughtException'; if (!(typeof window === 'undefined' && typeof global !== 'undefined' && {}.toString.call(global) === '[object global]')) { return; } - var configDefaults = Configuration.defaults; - configDefaults.environmentInfoCollector = new NodeEnvironmentInfoCollector(); - configDefaults.errorParser = new NodeErrorParser(); - configDefaults.requestInfoCollector = new NodeRequestInfoCollector(); - configDefaults.submissionClient = new NodeSubmissionClient(); - process.on('uncaughtException', function (error) { - ExceptionlessClient.default.submitUnhandledException(error, 'uncaughtException'); + var defaults = Configuration.defaults; + defaults.environmentInfoCollector = new NodeEnvironmentInfoCollector(); + defaults.errorParser = new NodeErrorParser(); + defaults.requestInfoCollector = new NodeRequestInfoCollector(); + defaults.submissionClient = new NodeSubmissionClient(); + process.on(uncaughtException, function (error) { + ExceptionlessClient.default.submitUnhandledException(error, uncaughtException); }); - process.on('beforeExit', function (code) { + process.on(beforeExit, function (code) { + function getExitCodeReason(code) { + if (code === 1) { + return 'Uncaught Fatal Exception'; + } + if (code === 3) { + return 'Internal JavaScript Parse Error'; + } + if (code === 4) { + return 'Internal JavaScript Evaluation Failure'; + } + if (code === 5) { + return 'Fatal Exception'; + } + if (code === 6) { + return 'Non-function Internal Exception Handler '; + } + if (code === 7) { + return 'Internal Exception Handler Run-Time Failure'; + } + if (code === 8) { + return 'Uncaught Exception'; + } + if (code === 9) { + return 'Invalid Argument'; + } + if (code === 10) { + return 'Internal JavaScript Run-Time Failure'; + } + if (code === 12) { + return 'Invalid Debug Argument'; + } + if (code > 128) { + return 'Signal Exits'; + } + return null; + } var client = ExceptionlessClient.default; - var message = _this.getExitCodeReason(code); + var message = getExitCodeReason(code); if (message !== null) { - client.submitLog('beforeExit', message, 'Error'); + client.submitLog(beforeExit, message, 'Error'); } client.config.queue.process(); }); }; - NodeBootstrapper.prototype.getExitCodeReason = function (code) { - if (code === 1) { - return 'Uncaught Fatal Exception'; - } - if (code === 3) { - return 'Internal JavaScript Parse Error'; - } - if (code === 4) { - return 'Internal JavaScript Evaluation Failure'; - } - if (code === 5) { - return 'Fatal Exception'; - } - if (code === 6) { - return 'Non-function Internal Exception Handler '; - } - if (code === 7) { - return 'Internal Exception Handler Run-Time Failure'; - } - if (code === 8) { - return 'Uncaught Exception'; - } - if (code === 9) { - return 'Invalid Argument'; - } - if (code === 10) { - return 'Internal JavaScript Run-Time Failure'; - } - if (code === 12) { - return 'Invalid Debug Argument'; - } - if (code > 128) { - return 'Signal Exits'; - } - return null; - }; return NodeBootstrapper; })(); exports.NodeBootstrapper = NodeBootstrapper; -var WebErrorParser = (function () { - function WebErrorParser() { +var DefaultErrorParser = (function () { + function DefaultErrorParser() { } - WebErrorParser.prototype.parse = function (context, exception) { - var stackTrace = !!context.contextData['@@_TraceKit.StackTrace'] - ? context.contextData['@@_TraceKit.StackTrace'] + DefaultErrorParser.prototype.parse = function (context, exception) { + function getParameters(parameters) { + var params = (typeof parameters === 'string' ? [parameters] : parameters) || []; + var result = []; + for (var index = 0; index < params.length; index++) { + result.push({ name: params[index] }); + } + return result; + } + function getStackFrames(context, stackFrames) { + var frames = []; + for (var index = 0; index < stackFrames.length; index++) { + var frame = stackFrames[index]; + frames.push({ + name: frame.func, + parameters: getParameters(frame.args), + file_name: frame.url, + line_number: frame.line, + column: frame.column + }); + } + return frames; + } + var traceKitStackTrace = '@@_TraceKit.StackTrace'; + var stackTrace = !!context.contextData[traceKitStackTrace] + ? context.contextData[traceKitStackTrace] : TraceKit.computeStackTrace(exception, 25); if (!stackTrace) { throw new Error('Unable to parse the exceptions stack trace.'); } - var error = { + return { type: stackTrace.name, message: stackTrace.message || exception.message, - stack_trace: this.getStackFrames(context, stackTrace.stack || []) + stack_trace: getStackFrames(context, stackTrace.stack || []) }; - context.event.data['@error'] = error; - }; - WebErrorParser.prototype.getStackFrames = function (context, stackFrames) { - var frames = []; - for (var index = 0; index < stackFrames.length; index++) { - var frame = stackFrames[index]; - frames.push({ - name: frame.func, - parameters: this.getParameters(frame.args), - file_name: frame.url, - line_number: frame.line, - column: frame.column - }); - } - return frames; - }; - WebErrorParser.prototype.getParameters = function (parameters) { - var params = (typeof parameters === 'string' ? [parameters] : parameters) || []; - var result = []; - for (var index = 0; index < params.length; index++) { - result.push({ name: params[index] }); - } - return result; }; - return WebErrorParser; + return DefaultErrorParser; })(); -exports.WebErrorParser = WebErrorParser; -var WebModuleCollector = (function () { - function WebModuleCollector() { +exports.DefaultErrorParser = DefaultErrorParser; +var DefaultModuleCollector = (function () { + function DefaultModuleCollector() { } - WebModuleCollector.prototype.getModules = function (context) { + DefaultModuleCollector.prototype.getModules = function (context) { if (document && document.getElementsByTagName) { return null; } @@ -2675,17 +2736,17 @@ var WebModuleCollector = (function () { } return modules; }; - return WebModuleCollector; + return DefaultModuleCollector; })(); -exports.WebModuleCollector = WebModuleCollector; -var WebRequestInfoCollector = (function () { - function WebRequestInfoCollector() { +exports.DefaultModuleCollector = DefaultModuleCollector; +var DefaultRequestInfoCollector = (function () { + function DefaultRequestInfoCollector() { } - WebRequestInfoCollector.prototype.getRequestInfo = function (context) { + DefaultRequestInfoCollector.prototype.getRequestInfo = function (context) { if (!document || !navigator || !location) { return null; } - var ri = { + var requestInfo = { user_agent: navigator.userAgent, is_secure: location.protocol === 'https:', host: location.hostname, @@ -2695,162 +2756,72 @@ var WebRequestInfoCollector = (function () { query_string: Utils.parseQueryString(location.search.substring(1)) }; if (document.referrer && document.referrer !== '') { - ri.referrer = document.referrer; + requestInfo.referrer = document.referrer; } - return ri; + return requestInfo; }; - return WebRequestInfoCollector; + return DefaultRequestInfoCollector; })(); -exports.WebRequestInfoCollector = WebRequestInfoCollector; -var DefaultSubmissionClient = (function (_super) { - __extends(DefaultSubmissionClient, _super); - function DefaultSubmissionClient() { - _super.apply(this, arguments); +exports.DefaultRequestInfoCollector = DefaultRequestInfoCollector; +var DefaultBootstrapper = (function () { + function DefaultBootstrapper() { } - DefaultSubmissionClient.prototype.createRequest = function (config, method, url) { - var xhr = new XMLHttpRequest(); - if ('withCredentials' in xhr) { - xhr.open(method, url, true); - } - else if (typeof XDomainRequest != 'undefined') { - xhr = new XDomainRequest(); - xhr.open(method, url); - } - else { - xhr = null; - } - if (xhr) { - xhr.setRequestHeader('X-Exceptionless-Client', config.userAgent); - if (method === 'POST' && xhr.setRequestHeader) { - xhr.setRequestHeader('Content-Type', 'application/json'); + DefaultBootstrapper.prototype.register = function () { + function getDefaultsSettingsFromScriptTag() { + if (!document || !document.getElementsByTagName) { + return null; } - xhr.timeout = 10000; - } - return xhr; - }; - DefaultSubmissionClient.prototype.sendRequest = function (config, method, path, data, callback) { - var isCompleted = false; - function complete(xhr) { - function parseResponseHeaders(headerStr) { - var headers = {}; - var headerPairs = (headerStr || '').split('\u000d\u000a'); - for (var index = 0; index < headerPairs.length; index++) { - var headerPair = headerPairs[index]; - var separator = headerPair.indexOf('\u003a\u0020'); - if (separator > 0) { - headers[headerPair.substring(0, separator)] = headerPair.substring(separator + 2); - } - } - return headers; - } - if (isCompleted) { - return; - } - else { - isCompleted = true; - } - var message; - if (xhr.status === 0) { - message = 'Unable to connect to server.'; - } - else if (xhr.status < 200 || xhr.status > 299) { - if (!!xhr.responseBody && !!xhr.responseBody.message) { - message = xhr.responseBody.message; - } - else if (!!xhr.responseText && xhr.responseText.indexOf('message') !== -1) { - try { - message = JSON.parse(xhr.responseText).message; - } - catch (e) { - message = xhr.responseText; - } - } - else { - message = xhr.statusText; + var scripts = document.getElementsByTagName('script'); + for (var index = 0; index < scripts.length; index++) { + if (scripts[index].src && scripts[index].src.indexOf('/exceptionless') > -1) { + return Utils.parseQueryString(scripts[index].src.split('?').pop()); } } - callback(xhr.status || 500, message, xhr.responseText, parseResponseHeaders(xhr.getAllResponseHeaders())); + return null; } - var url = "" + config.serverUrl + path + "?access_token=" + encodeURIComponent(config.apiKey); - var xhr = this.createRequest(config, method || 'POST', url); - if (!xhr) { - return callback(503, 'CORS not supported.'); + function processUnhandledException(stackTrace, options) { + var builder = ExceptionlessClient.default.createUnhandledException(new Error(stackTrace.message || (options || {}).status || 'Script error'), 'onerror'); + builder.pluginContextData['@@_TraceKit.StackTrace'] = stackTrace; + builder.submit(); } - if ('withCredentials' in xhr) { - xhr.onreadystatechange = function () { - if (xhr.readyState !== 4) { - return; - } - complete(xhr); - }; + function processJQueryAjaxError(event, xhr, settings, error) { + var client = ExceptionlessClient.default; + if (xhr.status === 404) { + client.submitNotFound(settings.url); + } + else if (xhr.status !== 401) { + client.createUnhandledException(error, 'JQuery.ajaxError') + .setSource(settings.url) + .setProperty('status', xhr.status) + .setProperty('request', settings.data) + .setProperty('response', xhr.responseText && xhr.responseText.slice && xhr.responseText.slice(0, 1024)) + .submit(); + } } - xhr.ontimeout = function () { return complete(xhr); }; - xhr.onerror = function () { return complete(xhr); }; - xhr.onload = function () { return complete(xhr); }; - xhr.send(data); - }; - return DefaultSubmissionClient; -})(SubmissionClientBase); -exports.DefaultSubmissionClient = DefaultSubmissionClient; -var WindowBootstrapper = (function () { - function WindowBootstrapper() { - } - WindowBootstrapper.prototype.register = function () { if (typeof window === 'undefined' || typeof document === 'undefined') { return; } - var configDefaults = Configuration.defaults; - var settings = this.getDefaultsSettingsFromScriptTag(); + var defaults = Configuration.defaults; + var settings = getDefaultsSettingsFromScriptTag(); if (settings && (settings.apiKey || settings.serverUrl)) { - configDefaults.apiKey = settings.apiKey; - configDefaults.serverUrl = settings.serverUrl; - } - configDefaults.errorParser = new WebErrorParser(); - configDefaults.moduleCollector = new WebModuleCollector(); - configDefaults.requestInfoCollector = new WebRequestInfoCollector(); - configDefaults.submissionClient = new DefaultSubmissionClient(); - TraceKit.report.subscribe(this.processUnhandledException); + defaults.apiKey = settings.apiKey; + defaults.serverUrl = settings.serverUrl; + } + defaults.errorParser = new DefaultErrorParser(); + defaults.moduleCollector = new DefaultModuleCollector(); + defaults.requestInfoCollector = new DefaultRequestInfoCollector(); + defaults.submissionClient = new DefaultSubmissionClient(); + TraceKit.report.subscribe(processUnhandledException); TraceKit.extendToAsynchronousCallbacks(); if (typeof $ !== 'undefined' && $(document)) { - $(document).ajaxError(this.processJQueryAjaxError); - } - }; - WindowBootstrapper.prototype.getDefaultsSettingsFromScriptTag = function () { - if (!document || !document.getElementsByTagName) { - return null; - } - var scripts = document.getElementsByTagName('script'); - for (var index = 0; index < scripts.length; index++) { - if (scripts[index].src && scripts[index].src.indexOf('/exceptionless') > -1) { - return Utils.parseQueryString(scripts[index].src.split('?').pop()); - } - } - return null; - }; - WindowBootstrapper.prototype.processUnhandledException = function (stackTrace, options) { - var builder = ExceptionlessClient.default.createUnhandledException(new Error(stackTrace.message || (options || {}).status || 'Script error'), 'onerror'); - builder.pluginContextData['@@_TraceKit.StackTrace'] = stackTrace; - builder.submit(); - }; - WindowBootstrapper.prototype.processJQueryAjaxError = function (event, xhr, settings, error) { - var client = ExceptionlessClient.default; - if (xhr.status === 404) { - client.submitNotFound(settings.url); - } - else if (xhr.status !== 401) { - client.createUnhandledException(error, 'JQuery.ajaxError') - .setSource(settings.url) - .setProperty('status', xhr.status) - .setProperty('request', settings.data) - .setProperty('response', xhr.responseText && xhr.responseText.slice ? xhr.responseText.slice(0, 1024) : undefined) - .submit(); + $(document).ajaxError(processJQueryAjaxError); } }; - return WindowBootstrapper; + return DefaultBootstrapper; })(); -exports.WindowBootstrapper = WindowBootstrapper; +exports.DefaultBootstrapper = DefaultBootstrapper; new NodeBootstrapper().register(); -new WindowBootstrapper().register(); +new DefaultBootstrapper().register(); Error.stackTraceLimit = Infinity; return exports; diff --git a/dist/exceptionless.js.map b/dist/exceptionless.js.map index bb9cbbd5..3bee0b93 100644 --- a/dist/exceptionless.js.map +++ b/dist/exceptionless.js.map @@ -1 +1 @@ -{"version":3,"sources":["tracekit.js","/source/exceptionless.ts"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7nCA,iCAAiC,8BAA8B,CAAC,CAAA;AAChE,mCAAmC,gCAAgC,CAAC,CAAA;AAyDpE,IAAI,mCAAgB,EAAE,CAAC,QAAQ,EAAE,CAAC;AAClC,IAAI,uCAAkB,EAAE,CAAC,QAAQ,EAAE,CAAC;AAC9B,KAAM,CAAC,eAAe,GAAG,QAAQ,CAAC","file":"exceptionless.js","sourcesContent":["/*\n TraceKit - Cross brower stack traces - github.com/csnover/TraceKit\n MIT license\n*/\n\n(function(window, undefined) {\nif (!window) {\n return;\n}\n\nvar TraceKit = {};\nvar _oldTraceKit = window.TraceKit;\n\n// global reference to slice\nvar _slice = [].slice;\nvar UNKNOWN_FUNCTION = '?';\n\n\n/**\n * _has, a better form of hasOwnProperty\n * Example: _has(MainHostObject, property) === true/false\n *\n * @param {Object} object to check property\n * @param {string} key to check\n */\nfunction _has(object, key) {\n return Object.prototype.hasOwnProperty.call(object, key);\n}\n\nfunction _isUndefined(what) {\n return typeof what === 'undefined';\n}\n\n/**\n * TraceKit.noConflict: Export TraceKit out to another variable\n * Example: var TK = TraceKit.noConflict()\n */\nTraceKit.noConflict = function noConflict() {\n window.TraceKit = _oldTraceKit;\n return TraceKit;\n};\n\n/**\n * TraceKit.wrap: Wrap any function in a TraceKit reporter\n * Example: func = TraceKit.wrap(func);\n *\n * @param {Function} func Function to be wrapped\n * @return {Function} The wrapped func\n */\nTraceKit.wrap = function traceKitWrapper(func) {\n function wrapped() {\n try {\n return func.apply(this, arguments);\n } catch (e) {\n TraceKit.report(e);\n throw e;\n }\n }\n return wrapped;\n};\n\n/**\n * TraceKit.report: cross-browser processing of unhandled exceptions\n *\n * Syntax:\n * TraceKit.report.subscribe(function(stackInfo) { ... })\n * TraceKit.report.unsubscribe(function(stackInfo) { ... })\n * TraceKit.report(exception)\n * try { ...code... } catch(ex) { TraceKit.report(ex); }\n *\n * Supports:\n * - Firefox: full stack trace with line numbers, plus column number\n * on top frame; column number is not guaranteed\n * - Opera: full stack trace with line and column numbers\n * - Chrome: full stack trace with line and column numbers\n * - Safari: line and column number for the top frame only; some frames\n * may be missing, and column number is not guaranteed\n * - IE: line and column number for the top frame only; some frames\n * may be missing, and column number is not guaranteed\n *\n * In theory, TraceKit should work on all of the following versions:\n * - IE5.5+ (only 8.0 tested)\n * - Firefox 0.9+ (only 3.5+ tested)\n * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require\n * Exceptions Have Stacktrace to be enabled in opera:config)\n * - Safari 3+ (only 4+ tested)\n * - Chrome 1+ (only 5+ tested)\n * - Konqueror 3.5+ (untested)\n *\n * Requires TraceKit.computeStackTrace.\n *\n * Tries to catch all unhandled exceptions and report them to the\n * subscribed handlers. Please note that TraceKit.report will rethrow the\n * exception. This is REQUIRED in order to get a useful stack trace in IE.\n * If the exception does not reach the top of the browser, you will only\n * get a stack trace from the point where TraceKit.report was called.\n *\n * Handlers receive a stackInfo object as described in the\n * TraceKit.computeStackTrace docs.\n */\nTraceKit.report = (function reportModuleWrapper() {\n var handlers = [],\n lastException = null,\n lastExceptionStack = null;\n\n /**\n * Add a crash handler.\n * @param {Function} handler\n */\n function subscribe(handler) {\n installGlobalHandler();\n handlers.push(handler);\n }\n\n /**\n * Remove a crash handler.\n * @param {Function} handler\n */\n function unsubscribe(handler) {\n for (var i = handlers.length - 1; i >= 0; --i) {\n if (handlers[i] === handler) {\n handlers.splice(i, 1);\n }\n }\n }\n\n /**\n * Dispatch stack information to all handlers.\n * @param {Object.} stack\n */\n function notifyHandlers(stack, isWindowError) {\n var exception = null;\n if (isWindowError && !TraceKit.collectWindowErrors) {\n return;\n }\n for (var i in handlers) {\n if (_has(handlers, i)) {\n try {\n handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2)));\n } catch (inner) {\n exception = inner;\n }\n }\n }\n\n if (exception) {\n throw exception;\n }\n }\n\n var _oldOnerrorHandler, _onErrorHandlerInstalled;\n\n /**\n * Ensures all global unhandled exceptions are recorded.\n * Supported by Gecko and IE.\n * @param {string} message Error message.\n * @param {string} url URL of script that generated the exception.\n * @param {(number|string)} lineNo The line number at which the error\n * occurred.\n * @param {?(number|string)} columnNo The column number at which the error\n * occurred.\n * @param {?Error} errorObj The actual Error object.\n */\n function traceKitWindowOnError(message, url, lineNo, columnNo, errorObj) {\n var stack = null;\n\n if (errorObj) {\n stack = TraceKit.computeStackTrace(errorObj);\n }\n else\n {\n if (lastExceptionStack) {\n TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message);\n stack = lastExceptionStack;\n lastExceptionStack = null;\n lastException = null;\n } else {\n var location = {\n 'url': url,\n 'line': lineNo,\n 'column': columnNo\n };\n location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line);\n location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line);\n stack = {\n 'mode': 'onerror',\n 'message': message,\n 'url': document.location.href,\n 'stack': [location],\n 'useragent': navigator.userAgent\n };\n }\n }\n\n notifyHandlers(stack, 'from window.onerror');\n\n if (_oldOnerrorHandler) {\n return _oldOnerrorHandler.apply(this, arguments);\n }\n\n return false;\n }\n\n function installGlobalHandler ()\n {\n if (_onErrorHandlerInstalled === true) {\n return;\n }\n _oldOnerrorHandler = window.onerror;\n window.onerror = traceKitWindowOnError;\n _onErrorHandlerInstalled = true;\n }\n\n /**\n * Reports an unhandled Error to TraceKit.\n * @param {Error} ex\n */\n function report(ex) {\n var args = _slice.call(arguments, 1);\n if (lastExceptionStack) {\n if (lastException === ex) {\n return; // already caught by an inner catch block, ignore\n } else {\n var s = lastExceptionStack;\n lastExceptionStack = null;\n lastException = null;\n notifyHandlers.apply(null, [s, null].concat(args));\n }\n }\n\n var stack = TraceKit.computeStackTrace(ex);\n lastExceptionStack = stack;\n lastException = ex;\n\n // If the stack trace is incomplete, wait for 2 seconds for\n // slow slow IE to see if onerror occurs or not before reporting\n // this exception; otherwise, we will end up with an incomplete\n // stack trace\n window.setTimeout(function () {\n if (lastException === ex) {\n lastExceptionStack = null;\n lastException = null;\n notifyHandlers.apply(null, [stack, null].concat(args));\n }\n }, (stack.incomplete ? 2000 : 0));\n\n throw ex; // re-throw to propagate to the top level (and cause window.onerror)\n }\n\n report.subscribe = subscribe;\n report.unsubscribe = unsubscribe;\n return report;\n}());\n\n/**\n * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript\n *\n * Syntax:\n * s = TraceKit.computeStackTrace.ofCaller([depth])\n * s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below)\n * Returns:\n * s.name - exception name\n * s.message - exception message\n * s.stack[i].url - JavaScript or HTML file URL\n * s.stack[i].func - function name, or empty for anonymous functions (if guessing did not work)\n * s.stack[i].args - arguments passed to the function, if known\n * s.stack[i].line - line number, if known\n * s.stack[i].column - column number, if known\n * s.stack[i].context - an array of source code lines; the middle element corresponds to the correct line#\n * s.mode - 'stack', 'stacktrace', 'multiline', 'callers', 'onerror', or 'failed' -- method used to collect the stack trace\n *\n * Supports:\n * - Firefox: full stack trace with line numbers and unreliable column\n * number on top frame\n * - Opera 10: full stack trace with line and column numbers\n * - Opera 9-: full stack trace with line numbers\n * - Chrome: full stack trace with line and column numbers\n * - Safari: line and column number for the topmost stacktrace element\n * only\n * - IE: no line numbers whatsoever\n *\n * Tries to guess names of anonymous functions by looking for assignments\n * in the source code. In IE and Safari, we have to guess source file names\n * by searching for function bodies inside all page scripts. This will not\n * work for scripts that are loaded cross-domain.\n * Here be dragons: some function names may be guessed incorrectly, and\n * duplicate functions may be mismatched.\n *\n * TraceKit.computeStackTrace should only be used for tracing purposes.\n * Logging of unhandled exceptions should be done with TraceKit.report,\n * which builds on top of TraceKit.computeStackTrace and provides better\n * IE support by utilizing the window.onerror event to retrieve information\n * about the top of the stack.\n *\n * Note: In IE and Safari, no stack trace is recorded on the Error object,\n * so computeStackTrace instead walks its *own* chain of callers.\n * This means that:\n * * in Safari, some methods may be missing from the stack trace;\n * * in IE, the topmost function in the stack trace will always be the\n * caller of computeStackTrace.\n *\n * This is okay for tracing (because you are likely to be calling\n * computeStackTrace from the function you want to be the topmost element\n * of the stack trace anyway), but not okay for logging unhandled\n * exceptions (because your catch block will likely be far away from the\n * inner function that actually caused the exception).\n *\n * Tracing example:\n * function trace(message) {\n * var stackInfo = TraceKit.computeStackTrace.ofCaller();\n * var data = message + \"\\n\";\n * for(var i in stackInfo.stack) {\n * var item = stackInfo.stack[i];\n * data += (item.func || '[anonymous]') + \"() in \" + item.url + \":\" + (item.line || '0') + \"\\n\";\n * }\n * if (window.console)\n * console.info(data);\n * else\n * alert(data);\n * }\n */\nTraceKit.computeStackTrace = (function computeStackTraceWrapper() {\n var debug = false,\n sourceCache = {};\n\n /**\n * Attempts to retrieve source code via XMLHttpRequest, which is used\n * to look up anonymous function names.\n * @param {string} url URL of source code.\n * @return {string} Source contents.\n */\n function loadSource(url) {\n if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on.\n return '';\n }\n try {\n var getXHR = function() {\n try {\n return new window.XMLHttpRequest();\n } catch (e) {\n // explicitly bubble up the exception if not found\n return new window.ActiveXObject('Microsoft.XMLHTTP');\n }\n };\n\n var request = getXHR();\n request.open('GET', url, false);\n request.send('');\n return request.responseText;\n } catch (e) {\n return '';\n }\n }\n\n /**\n * Retrieves source code from the source code cache.\n * @param {string} url URL of source code.\n * @return {Array.} Source contents.\n */\n function getSource(url) {\n if (typeof url !== 'string') {\n return [];\n }\n\n if (!_has(sourceCache, url)) {\n // URL needs to be able to fetched within the acceptable domain. Otherwise,\n // cross-domain errors will be triggered.\n var source = '';\n\n url = url || '';\n if (url.indexOf && url.indexOf(document.domain) !== -1) {\n source = loadSource(url);\n }\n sourceCache[url] = source ? source.split('\\n') : [];\n }\n\n return sourceCache[url];\n }\n\n /**\n * Tries to use an externally loaded copy of source code to determine\n * the name of a function by looking at the name of the variable it was\n * assigned to, if any.\n * @param {string} url URL of source code.\n * @param {(string|number)} lineNo Line number in source code.\n * @return {string} The function name, if discoverable.\n */\n function guessFunctionName(url, lineNo) {\n var reFunctionArgNames = /function ([^(]*)\\(([^)]*)\\)/,\n reGuessFunction = /['\"]?([0-9A-Za-z$_]+)['\"]?\\s*[:=]\\s*(function|eval|new Function)/,\n line = '',\n maxLines = 10,\n source = getSource(url),\n m;\n\n if (!source.length) {\n return UNKNOWN_FUNCTION;\n }\n\n // Walk backwards from the first line in the function until we find the line which\n // matches the pattern above, which is the function definition\n for (var i = 0; i < maxLines; ++i) {\n line = source[lineNo - i] + line;\n\n if (!_isUndefined(line)) {\n if ((m = reGuessFunction.exec(line))) {\n return m[1];\n } else if ((m = reFunctionArgNames.exec(line))) {\n return m[1];\n }\n }\n }\n\n return UNKNOWN_FUNCTION;\n }\n\n /**\n * Retrieves the surrounding lines from where an exception occurred.\n * @param {string} url URL of source code.\n * @param {(string|number)} line Line number in source code to centre\n * around for context.\n * @return {?Array.} Lines of source code.\n */\n function gatherContext(url, line) {\n var source = getSource(url);\n\n if (!source.length) {\n return null;\n }\n\n var context = [],\n // linesBefore & linesAfter are inclusive with the offending line.\n // if linesOfContext is even, there will be one extra line\n // *before* the offending line.\n linesBefore = Math.floor(TraceKit.linesOfContext / 2),\n // Add one extra line if linesOfContext is odd\n linesAfter = linesBefore + (TraceKit.linesOfContext % 2),\n start = Math.max(0, line - linesBefore - 1),\n end = Math.min(source.length, line + linesAfter - 1);\n\n line -= 1; // convert to 0-based index\n\n for (var i = start; i < end; ++i) {\n if (!_isUndefined(source[i])) {\n context.push(source[i]);\n }\n }\n\n return context.length > 0 ? context : null;\n }\n\n /**\n * Escapes special characters, except for whitespace, in a string to be\n * used inside a regular expression as a string literal.\n * @param {string} text The string.\n * @return {string} The escaped string literal.\n */\n function escapeRegExp(text) {\n return text.replace(/[\\-\\[\\]{}()*+?.,\\\\\\^$|#]/g, '\\\\$&');\n }\n\n /**\n * Escapes special characters in a string to be used inside a regular\n * expression as a string literal. Also ensures that HTML entities will\n * be matched the same as their literal friends.\n * @param {string} body The string.\n * @return {string} The escaped string.\n */\n function escapeCodeAsRegExpForMatchingInsideHTML(body) {\n return escapeRegExp(body).replace('<', '(?:<|<)').replace('>', '(?:>|>)').replace('&', '(?:&|&)').replace('\"', '(?:\"|")').replace(/\\s+/g, '\\\\s+');\n }\n\n /**\n * Determines where a code fragment occurs in the source code.\n * @param {RegExp} re The function definition.\n * @param {Array.} urls A list of URLs to search.\n * @return {?Object.} An object containing\n * the url, line, and column number of the defined function.\n */\n function findSourceInUrls(re, urls) {\n var source, m;\n for (var i = 0, j = urls.length; i < j; ++i) {\n // console.log('searching', urls[i]);\n if ((source = getSource(urls[i])).length) {\n source = source.join('\\n');\n if ((m = re.exec(source))) {\n // console.log('Found function in ' + urls[i]);\n\n return {\n 'url': urls[i],\n 'line': source.substring(0, m.index).split('\\n').length,\n 'column': m.index - source.lastIndexOf('\\n', m.index) - 1\n };\n }\n }\n }\n\n // console.log('no match');\n\n return null;\n }\n\n /**\n * Determines at which column a code fragment occurs on a line of the\n * source code.\n * @param {string} fragment The code fragment.\n * @param {string} url The URL to search.\n * @param {(string|number)} line The line number to examine.\n * @return {?number} The column number.\n */\n function findSourceInLine(fragment, url, line) {\n var source = getSource(url),\n re = new RegExp('\\\\b' + escapeRegExp(fragment) + '\\\\b'),\n m;\n\n line -= 1;\n\n if (source && source.length > line && (m = re.exec(source[line]))) {\n return m.index;\n }\n\n return null;\n }\n\n /**\n * Determines where a function was defined within the source code.\n * @param {(Function|string)} func A function reference or serialized\n * function definition.\n * @return {?Object.} An object containing\n * the url, line, and column number of the defined function.\n */\n function findSourceByFunctionBody(func) {\n var urls = [window.location.href],\n scripts = document.getElementsByTagName('script'),\n body,\n code = '' + func,\n codeRE = /^function(?:\\s+([\\w$]+))?\\s*\\(([\\w\\s,]*)\\)\\s*\\{\\s*(\\S[\\s\\S]*\\S)\\s*\\}\\s*$/,\n eventRE = /^function on([\\w$]+)\\s*\\(event\\)\\s*\\{\\s*(\\S[\\s\\S]*\\S)\\s*\\}\\s*$/,\n re,\n parts,\n result;\n\n for (var i = 0; i < scripts.length; ++i) {\n var script = scripts[i];\n if (script.src) {\n urls.push(script.src);\n }\n }\n\n if (!(parts = codeRE.exec(code))) {\n re = new RegExp(escapeRegExp(code).replace(/\\s+/g, '\\\\s+'));\n }\n\n // not sure if this is really necessary, but I don’t have a test\n // corpus large enough to confirm that and it was in the original.\n else {\n var name = parts[1] ? '\\\\s+' + parts[1] : '',\n args = parts[2].split(',').join('\\\\s*,\\\\s*');\n\n body = escapeRegExp(parts[3]).replace(/;$/, ';?'); // semicolon is inserted if the function ends with a comment.replace(/\\s+/g, '\\\\s+');\n re = new RegExp('function' + name + '\\\\s*\\\\(\\\\s*' + args + '\\\\s*\\\\)\\\\s*{\\\\s*' + body + '\\\\s*}');\n }\n\n // look for a normal function definition\n if ((result = findSourceInUrls(re, urls))) {\n return result;\n }\n\n // look for an old-school event handler function\n if ((parts = eventRE.exec(code))) {\n var event = parts[1];\n body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]);\n\n // look for a function defined in HTML as an onXXX handler\n re = new RegExp('on' + event + '=[\\\\\\'\"]\\\\s*' + body + '\\\\s*[\\\\\\'\"]', 'i');\n\n if ((result = findSourceInUrls(re, urls[0]))) {\n return result;\n }\n\n // look for ???\n re = new RegExp(body);\n\n if ((result = findSourceInUrls(re, urls))) {\n return result;\n }\n }\n\n return null;\n }\n\n // Contents of Exception in various browsers.\n //\n // SAFARI:\n // ex.message = Can't find variable: qq\n // ex.line = 59\n // ex.sourceId = 580238192\n // ex.sourceURL = http://...\n // ex.expressionBeginOffset = 96\n // ex.expressionCaretOffset = 98\n // ex.expressionEndOffset = 98\n // ex.name = ReferenceError\n //\n // FIREFOX:\n // ex.message = qq is not defined\n // ex.fileName = http://...\n // ex.lineNumber = 59\n // ex.columnNumber = 69\n // ex.stack = ...stack trace... (see the example below)\n // ex.name = ReferenceError\n //\n // CHROME:\n // ex.message = qq is not defined\n // ex.name = ReferenceError\n // ex.type = not_defined\n // ex.arguments = ['aa']\n // ex.stack = ...stack trace...\n //\n // INTERNET EXPLORER:\n // ex.message = ...\n // ex.name = ReferenceError\n //\n // OPERA:\n // ex.message = ...message... (see the example below)\n // ex.name = ReferenceError\n // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message)\n // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace'\n\n /**\n * Computes stack trace information from the stack property.\n * Chrome and Gecko use this property.\n * @param {Error} ex\n * @return {?Object.} Stack trace information.\n */\n function computeStackTraceFromStackProp(ex) {\n if (!ex.stack) {\n return null;\n }\n\n var chrome = /^\\s*at (.*?) ?\\(?((?:file|https?|chrome-extension):.*?):(\\d+)(?::(\\d+))?\\)?\\s*$/i,\n gecko = /^\\s*(.*?)(?:\\((.*?)\\))?@?((?:file|https?|chrome):.*?):(\\d+)(?::(\\d+))?\\s*$/i,\n winjs = /^\\s*at (?:((?:\\[object object\\])?.+) )?\\(?((?:ms-appx|http|https):.*?):(\\d+)(?::(\\d+))?\\)?\\s*$/i,\n lines = ex.stack.split('\\n'),\n stack = [],\n parts,\n element,\n reference = /^(.*) is undefined$/.exec(ex.message);\n\n for (var i = 0, j = lines.length; i < j; ++i) {\n if ((parts = gecko.exec(lines[i]))) {\n element = {\n 'url': parts[3],\n 'func': parts[1] || UNKNOWN_FUNCTION,\n 'args': parts[2] ? parts[2].split(',') : '',\n 'line': +parts[4],\n 'column': parts[5] ? +parts[5] : null\n };\n } else if ((parts = chrome.exec(lines[i]))) {\n element = {\n 'url': parts[2],\n 'func': parts[1] || UNKNOWN_FUNCTION,\n 'line': +parts[3],\n 'column': parts[4] ? +parts[4] : null\n };\n } else if ((parts = winjs.exec(lines[i]))) {\n element = {\n 'url': parts[2],\n 'func': parts[1] || UNKNOWN_FUNCTION,\n 'line': +parts[3],\n 'column': parts[4] ? +parts[4] : null\n };\n } else {\n continue;\n }\n\n if (!element.func && element.line) {\n element.func = guessFunctionName(element.url, element.line);\n }\n\n if (element.line) {\n element.context = gatherContext(element.url, element.line);\n }\n\n stack.push(element);\n }\n\n if (!stack.length) {\n return null;\n }\n\n if (stack[0] && stack[0].line && !stack[0].column && reference) {\n stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line);\n } else if (!stack[0].column && !_isUndefined(ex.columnNumber)) {\n // FireFox uses this awesome columnNumber property for its top frame\n // Also note, Firefox's column number is 0-based and everything else expects 1-based,\n // so adding 1\n stack[0].column = ex.columnNumber + 1;\n }\n\n return {\n 'mode': 'stack',\n 'name': ex.name,\n 'message': ex.message,\n 'url': document.location.href,\n 'stack': stack,\n 'useragent': navigator.userAgent\n };\n }\n\n /**\n * Computes stack trace information from the stacktrace property.\n * Opera 10 uses this property.\n * @param {Error} ex\n * @return {?Object.} Stack trace information.\n */\n function computeStackTraceFromStacktraceProp(ex) {\n // Access and store the stacktrace property before doing ANYTHING\n // else to it because Opera is not very good at providing it\n // reliably in other circumstances.\n var stacktrace = ex.stacktrace;\n\n var testRE = / line (\\d+), column (\\d+) in (?:]+)>|([^\\)]+))\\((.*)\\) in (.*):\\s*$/i,\n lines = stacktrace.split('\\n'),\n stack = [],\n parts;\n\n for (var i = 0, j = lines.length; i < j; i += 2) {\n if ((parts = testRE.exec(lines[i]))) {\n var element = {\n 'line': +parts[1],\n 'column': +parts[2],\n 'func': parts[3] || parts[4],\n 'args': parts[5] ? parts[5].split(',') : [],\n 'url': parts[6]\n };\n\n if (!element.func && element.line) {\n element.func = guessFunctionName(element.url, element.line);\n }\n if (element.line) {\n try {\n element.context = gatherContext(element.url, element.line);\n } catch (exc) {}\n }\n\n if (!element.context) {\n element.context = [lines[i + 1]];\n }\n\n stack.push(element);\n }\n }\n\n if (!stack.length) {\n return null;\n }\n\n return {\n 'mode': 'stacktrace',\n 'name': ex.name,\n 'message': ex.message,\n 'url': document.location.href,\n 'stack': stack,\n 'useragent': navigator.userAgent\n };\n }\n\n /**\n * NOT TESTED.\n * Computes stack trace information from an error message that includes\n * the stack trace.\n * Opera 9 and earlier use this method if the option to show stack\n * traces is turned on in opera:config.\n * @param {Error} ex\n * @return {?Object.} Stack information.\n */\n function computeStackTraceFromOperaMultiLineMessage(ex) {\n // Opera includes a stack trace into the exception message. An example is:\n //\n // Statement on line 3: Undefined variable: undefinedFunc\n // Backtrace:\n // Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz\n // undefinedFunc(a);\n // Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy\n // zzz(x, y, z);\n // Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx\n // yyy(a, a, a);\n // Line 1 of function script\n // try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); }\n // ...\n\n var lines = ex.message.split('\\n');\n if (lines.length < 4) {\n return null;\n }\n\n var lineRE1 = /^\\s*Line (\\d+) of linked script ((?:file|https?)\\S+)(?:: in function (\\S+))?\\s*$/i,\n lineRE2 = /^\\s*Line (\\d+) of inline#(\\d+) script in ((?:file|https?)\\S+)(?:: in function (\\S+))?\\s*$/i,\n lineRE3 = /^\\s*Line (\\d+) of function script\\s*$/i,\n stack = [],\n scripts = document.getElementsByTagName('script'),\n inlineScriptBlocks = [],\n parts,\n i,\n len,\n source;\n\n for (i in scripts) {\n if (_has(scripts, i) && !scripts[i].src) {\n inlineScriptBlocks.push(scripts[i]);\n }\n }\n\n for (i = 2, len = lines.length; i < len; i += 2) {\n var item = null;\n if ((parts = lineRE1.exec(lines[i]))) {\n item = {\n 'url': parts[2],\n 'func': parts[3],\n 'line': +parts[1]\n };\n } else if ((parts = lineRE2.exec(lines[i]))) {\n item = {\n 'url': parts[3],\n 'func': parts[4]\n };\n var relativeLine = (+parts[1]); // relative to the start of the