From 4952b91bc018a47c576a78078e6f02c34b320b7f Mon Sep 17 00:00:00 2001 From: Frank Ebersoll Date: Thu, 15 Oct 2015 22:49:42 +0200 Subject: [PATCH 1/7] Removed "tsd reinstall" from appveyor script --- appveyor.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 65a88e3e..fe4513cd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,7 +9,6 @@ install: - npm install -g gulp - npm install -g bower - npm install - - tsd reinstall -s - bower install build_script: From 614c14e6d7d1ca801fc7857ec729b1cb8d696ab2 Mon Sep 17 00:00:00 2001 From: Frank Ebersoll Date: Fri, 16 Oct 2015 02:35:29 +0200 Subject: [PATCH 2/7] Added NodeModuleCollector --- dist/exceptionless.node.js | 59 +++++++++++++++++++++++++ dist/exceptionless.node.js.map | 2 +- src/exceptionless.node.ts | 2 + src/services/NodeModuleCollector.ts | 68 +++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 src/services/NodeModuleCollector.ts diff --git a/dist/exceptionless.node.js b/dist/exceptionless.node.js index b13545d0..7184b3e9 100644 --- a/dist/exceptionless.node.js +++ b/dist/exceptionless.node.js @@ -1199,6 +1199,7 @@ var SettingsResponse = (function () { exports.SettingsResponse = SettingsResponse; var os = require('os'); var nodestacktrace = require('stack-trace'); +var path = require('path'); var https = require('https'); var url = require('url'); var NodeEnvironmentInfoCollector = (function () { @@ -1283,6 +1284,63 @@ var NodeErrorParser = (function () { return NodeErrorParser; })(); exports.NodeErrorParser = NodeErrorParser; +var NodeModuleCollector = (function () { + function NodeModuleCollector() { + this.initialized = false; + this.installedModules = {}; + } + NodeModuleCollector.prototype.getModules = function (context) { + var _this = this; + this.initialize(); + if (!require.main) + return []; + var modulePath = path.dirname(require.main.filename) + '/node_modules/'; + var pathLength = modulePath.length; + var loadedKeys = Object.keys(require.cache); + var loadedModules = {}; + loadedKeys.forEach(function (key) { + var id = key.substr(pathLength); + console.log(id); + id = id.substr(0, id.indexOf('/')); + loadedModules[id] = true; + }); + return Object.keys(loadedModules) + .map(function (key) { return _this.installedModules[key]; }) + .filter(function (m) { return m !== undefined; }); + }; + NodeModuleCollector.prototype.initialize = function () { + var _this = this; + if (this.initialized) + return; + this.initialized = true; + var output = child.spawnSync('npm', ['ls', '--depth=0', '--json']).stdout; + if (!output) + return; + var json; + try { + json = JSON.parse(output.toString()); + } + catch (e) { + return; + } + var items = json.dependencies; + if (!items) + return; + var id = 0; + this.installedModules = {}; + Object.keys(items).forEach(function (key) { + var item = items[key]; + var theModule = { + module_id: id++, + name: key, + version: item.version + }; + _this.installedModules[key] = theModule; + }); + }; + return NodeModuleCollector; +})(); +exports.NodeModuleCollector = NodeModuleCollector; var NodeRequestInfoCollector = (function () { function NodeRequestInfoCollector() { } @@ -1378,6 +1436,7 @@ var SIGINT_CODE = 2; var defaults = Configuration.defaults; defaults.environmentInfoCollector = new NodeEnvironmentInfoCollector(); defaults.errorParser = new NodeErrorParser(); +defaults.moduleCollector = new NodeModuleCollector(); defaults.requestInfoCollector = new NodeRequestInfoCollector(); defaults.submissionAdapter = new NodeSubmissionAdapter(); function getListenerCount(emitter, event) { diff --git a/dist/exceptionless.node.js.map b/dist/exceptionless.node.js.map index 09d0bf26..1828a510 100644 --- a/dist/exceptionless.node.js.map +++ b/dist/exceptionless.node.js.map @@ -1 +1 @@ -{"version":3,"file":"exceptionless.node.js","sourceRoot":"/source/","sources":["exceptionless.node.ts"],"names":["getListenerCount","onUncaughtException","getExitCodeReason"],"mappings":"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","sourcesContent":["import * as events from \"events\";\nimport * as net from \"net\";\nimport * as stream from \"stream\";\nimport * as child from \"child_process\";\nimport * as tls from \"tls\";\nimport * as http from \"http\";\nimport * as crypto from \"crypto\";\nexport interface IEvent {\n type?:string;\n source?:string;\n date?:Date;\n tags?:string[];\n message?:string;\n geo?:string;\n value?:number;\n data?:any;\n reference_id?:string;\n session_id?:string;\n}\n\nexport interface ILastReferenceIdManager {\n getLast(): string;\n clearLast(): void;\n setLast(eventId:string): void;\n}\n\nexport interface ILog {\n info(message:string):void;\n warn(message:string):void;\n error(message:string):void;\n}\n\n \n\nexport interface IEventQueue {\n enqueue(event:IEvent):void;\n process(isAppExiting?:boolean):void;\n suspendProcessing(durationInMinutes?:number, discardFutureQueuedItems?:boolean, clearQueue?:boolean):void;\n}\n\n \n\nexport interface IEnvironmentInfoCollector {\n getEnvironmentInfo(context:EventPluginContext):IEnvironmentInfo;\n}\n\n \n\nexport interface IErrorParser {\n parse(context:EventPluginContext, exception:Error): IError;\n}\n\n \n\nexport interface IModuleCollector {\n getModules(context:EventPluginContext):IModule[];\n}\n\n \n\nexport interface IRequestInfoCollector {\n getRequestInfo(context:EventPluginContext):IRequestInfo;\n}\n\n \n\nexport interface IStorage {\n save(path:string, value:T):boolean;\n get(path:string):T;\n getList(searchPattern?:string, limit?:number):IStorageItem[];\n remove(path:string):void;\n}\n\n \n\nexport interface ISubmissionAdapter {\n sendRequest(request:SubmissionRequest, callback:SubmissionCallback, isAppExiting?:boolean): void\n}\n\n \n\nexport interface ISubmissionClient {\n postEvents(events:IEvent[], config:Configuration, callback:(response:SubmissionResponse) => void, isAppExiting?:boolean):void;\n postUserDescription(referenceId:string, description:IUserDescription, config:Configuration, callback:(response:SubmissionResponse) => void):void;\n getSettings(config:Configuration, callback:(response:SettingsResponse) => void):void;\n}\n\n \n\nexport interface IConfigurationSettings {\n apiKey?:string;\n serverUrl?:string;\n environmentInfoCollector?:IEnvironmentInfoCollector;\n errorParser?:IErrorParser;\n lastReferenceIdManager?:ILastReferenceIdManager;\n log?:ILog;\n moduleCollector?:IModuleCollector;\n requestInfoCollector?:IRequestInfoCollector;\n submissionBatchSize?:number;\n submissionClient?:ISubmissionClient;\n submissionAdapter?:ISubmissionAdapter;\n storage?:IStorage;\n queue?:IEventQueue;\n}\n\n \n\nexport class SettingsManager {\n /**\n * The configuration settings path.\n * @type {string}\n * @private\n */\n private static _configPath:string = 'ex-server-settings.json';\n\n /**\n * A list of handlers that will be fired when the settings change.\n * @type {Array}\n * @private\n */\n private static _handlers:{ (config:Configuration):void }[] = [];\n\n private static changed(config:Configuration) {\n var handlers = this._handlers; // optimization for minifier.\n for (var index = 0; index < handlers.length; index++) {\n handlers[index](config);\n }\n }\n\n public static onChanged(handler:(config:Configuration) => void) {\n !!handler && this._handlers.push(handler);\n }\n\n public static applySavedServerSettings(config:Configuration):void {\n config.log.info('Applying saved settings.');\n config.settings = Utils.merge(config.settings, this.getSavedServerSettings(config));\n this.changed(config);\n }\n\n private static getSavedServerSettings(config:Configuration):Object {\n return config.storage.get(this._configPath) || {};\n }\n\n public static checkVersion(version:number, config:Configuration):void {\n if (version) {\n var savedConfigVersion = parseInt(config.storage.get(`${this._configPath}-version`));\n if (isNaN(savedConfigVersion) || version > savedConfigVersion) {\n config.log.info(`Updating settings from v${(!isNaN(savedConfigVersion) ? savedConfigVersion : 0)} to v${version}`);\n this.updateSettings(config);\n }\n }\n }\n\n public static updateSettings(config:Configuration):void {\n if (!config.isValid) {\n config.log.error('Unable to update settings: ApiKey is not set.');\n return;\n }\n\n config.submissionClient.getSettings(config, (response:SettingsResponse) => {\n if (!response || !response.success || !response.settings) {\n return;\n }\n\n config.settings = Utils.merge(config.settings, response.settings);\n\n // TODO: Store snapshot of settings after reading from config and attributes and use that to revert to defaults.\n // Remove any existing server settings that are not in the new server settings.\n var savedServerSettings = SettingsManager.getSavedServerSettings(config);\n for (var key in savedServerSettings) {\n if (response.settings[key]) {\n continue;\n }\n\n delete config.settings[key];\n }\n\n var path = SettingsManager._configPath; // optimization for minifier.\n config.storage.save(`${path}-version`, response.settingsVersion);\n config.storage.save(path, response.settings);\n\n config.log.info('Updated settings');\n this.changed(config);\n });\n }\n}\n\n \n\nexport class DefaultLastReferenceIdManager implements ILastReferenceIdManager {\n /**\n * Gets the last event's reference id that was submitted to the server.\n * @type {string}\n * @private\n */\n private _lastReferenceId:string = null;\n\n /**\n * Gets the last event's reference id that was submitted to the server.\n * @returns {string}\n */\n getLast(): string {\n return this._lastReferenceId;\n }\n\n /**\n * Clears the last event's reference id.\n */\n clearLast():void {\n this._lastReferenceId = null;\n }\n\n /**\n * Sets the last event's reference id.\n * @param eventId\n */\n setLast(eventId:string):void {\n this._lastReferenceId = eventId;\n }\n}\n\n \n\nexport class ConsoleLog implements ILog {\n public info(message:string):void {\n this.log('info', message);\n }\n\n public warn(message:string):void {\n this.log('warn', message);\n }\n\n public error(message:string):void {\n this.log('error', message);\n }\n\n private log(level:string, message:string) {\n if (console && console[level]) {\n console[level](`[${level}] Exceptionless: ${message}`);\n }\n }\n}\n\n \n\nexport class NullLog implements ILog {\n public info(message:string):void {}\n public warn(message:string):void {}\n public error(message:string):void {}\n}\n\nexport interface IUserInfo {\n identity?:string;\n name?:string;\n data?:any;\n}\n\n \n\nexport interface IEventPlugin {\n priority?:number;\n name?:string;\n run(context:EventPluginContext, next?:() => void): void;\n}\n\n \n\nexport class EventPluginContext {\n public cancelled:boolean;\n public client:ExceptionlessClient;\n public event:IEvent;\n public contextData:ContextData;\n\n constructor(client:ExceptionlessClient, event:IEvent, contextData?:ContextData) {\n this.client = client;\n this.event = event;\n this.contextData = contextData ? contextData : new ContextData();\n }\n\n public get log(): ILog {\n return this.client.config.log;\n }\n}\n\n \n\nexport class EventPluginManager {\n public static run(context:EventPluginContext, callback:(context?:EventPluginContext) => void): void {\n var wrap = function (plugin:IEventPlugin, next?:() => void): () => void {\n return () => {\n try {\n if (!context.cancelled) {\n plugin.run(context, next);\n }\n } catch (ex) {\n context.cancelled = true;\n context.log.error(`Error running plugin '${plugin.name}': ${ex.message}. Discarding Event.`);\n }\n\n if (context.cancelled && !!callback) {\n callback(context);\n }\n };\n };\n\n var plugins:IEventPlugin[] = context.client.config.plugins; // optimization for minifier.\n var wrappedPlugins:{ (): void }[] = [];\n if (!!callback) {\n wrappedPlugins[plugins.length] = wrap({ name: 'cb', priority: 9007199254740992, run: callback }, null);\n }\n\n for (var index = plugins.length - 1; index > -1; index--) {\n wrappedPlugins[index] = wrap(plugins[index], !!callback || (index < plugins.length - 1) ? wrappedPlugins[index + 1] : null);\n }\n\n wrappedPlugins[0]();\n }\n\n public static addDefaultPlugins(config:Configuration): void {\n config.addPlugin(new ConfigurationDefaultsPlugin());\n config.addPlugin(new ErrorPlugin());\n config.addPlugin(new ModuleInfoPlugin());\n config.addPlugin(new RequestInfoPlugin());\n config.addPlugin(new EnvironmentInfoPlugin());\n config.addPlugin(new SubmissionMethodPlugin());\n }\n}\n\n \n\nexport class ReferenceIdPlugin implements IEventPlugin {\n public priority:number = 20;\n public name:string = 'ReferenceIdPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n if ((!context.event.reference_id || context.event.reference_id.length === 0) && context.event.type === 'error') {\n context.event.reference_id = Utils.guid().replace('-', '').substring(0, 10);\n }\n\n next && next();\n }\n}\n\n \n\nexport class DefaultEventQueue implements IEventQueue {\n /**\n * The configuration object.\n * @type {Configuration}\n * @private\n */\n private _config:Configuration;\n\n /**\n * Suspends processing until the specified time.\n * @type {Date}\n * @private\n */\n private _suspendProcessingUntil:Date;\n\n /**\n * Discards queued items until the specified time.\n * @type {Date}\n * @private\n */\n private _discardQueuedItemsUntil:Date;\n\n /**\n * Returns true if the queue is processing.\n * @type {boolean}\n * @private\n */\n private _processingQueue:boolean = false;\n\n /**\n * Processes the queue every xx seconds.\n * @type {Timer}\n * @private\n */\n private _queueTimer:any;\n\n constructor(config:Configuration) {\n this._config = config;\n }\n\n public enqueue(event:IEvent): void {\n var config:Configuration = this._config; // Optimization for minifier.\n this.ensureQueueTimer();\n\n if (this.areQueuedItemsDiscarded()) {\n config.log.info('Queue items are currently being discarded. The event will not be queued.');\n return;\n }\n\n var key = `ex-q-${new Date().toJSON()}-${Utils.randomNumber()}`;\n config.log.info(`Enqueuing event: ${key} type=${event.type} ${!!event.reference_id ? 'refid=' + event.reference_id : ''}`);\n config.storage.save(key, event);\n }\n\n public process(isAppExiting?:boolean): void {\n function getEvents(events:{ path:string, value:IEvent }[]):IEvent[] {\n var items:IEvent[] = [];\n for (var index = 0; index < events.length; index++) {\n items.push(events[index].value);\n }\n\n return items;\n }\n\n const queueNotProcessed:string = 'The queue will not be processed.'; // optimization for minifier.\n var config:Configuration = this._config; // Optimization for minifier.\n var log:ILog = config.log; // Optimization for minifier.\n\n this.ensureQueueTimer();\n\n if (this._processingQueue) {\n return;\n }\n\n log.info('Processing queue...');\n if (!config.enabled) {\n log.info(`Configuration is disabled. ${queueNotProcessed}`);\n return;\n }\n\n if (!config.isValid) {\n log.info(`Invalid Api Key. ${queueNotProcessed}`);\n return;\n }\n\n this._processingQueue = true;\n\n try {\n var events = config.storage.getList('ex-q', config.submissionBatchSize);\n if (!events || events.length == 0) {\n this._processingQueue = false;\n return;\n }\n\n log.info(`Sending ${events.length} events to ${config.serverUrl}.`);\n config.submissionClient.postEvents(getEvents(events), config, (response:SubmissionResponse) => {\n this.processSubmissionResponse(response, events);\n log.info('Finished processing queue.');\n this._processingQueue = false;\n }, isAppExiting);\n } catch (ex) {\n log.error(`Error processing queue: ${ex}`);\n this.suspendProcessing();\n this._processingQueue = false;\n }\n }\n\n private processSubmissionResponse(response:SubmissionResponse, events:{ path:string, value:IEvent }[]): void {\n const noSubmission:string = 'The event will not be submitted.'; // Optimization for minifier.\n var config:Configuration = this._config; // Optimization for minifier.\n var log:ILog = config.log; // Optimization for minifier.\n\n if (response.success) {\n log.info(`Sent ${events.length} events.`);\n this.removeEvents(events);\n return;\n }\n\n if (response.serviceUnavailable) {\n // You are currently over your rate limit or the servers are under stress.\n log.error('Server returned service unavailable.');\n this.suspendProcessing();\n return;\n }\n\n if (response.paymentRequired) {\n // If the organization over the rate limit then discard the event.\n log.info('Too many events have been submitted, please upgrade your plan.');\n this.suspendProcessing(null, true, true);\n return;\n }\n\n if (response.unableToAuthenticate) {\n // The api key was suspended or could not be authorized.\n log.info(`Unable to authenticate, please check your configuration. ${noSubmission}`);\n this.suspendProcessing(15);\n this.removeEvents(events);\n return;\n }\n\n if (response.notFound || response.badRequest) {\n // The service end point could not be found.\n log.error(`Error while trying to submit data: ${response.message}`);\n this.suspendProcessing(60 * 4);\n this.removeEvents(events);\n return;\n }\n\n if (response.requestEntityTooLarge) {\n var message = 'Event submission discarded for being too large.';\n if (config.submissionBatchSize > 1) {\n log.error(`${message} Retrying with smaller batch size.`);\n config.submissionBatchSize = Math.max(1, Math.round(config.submissionBatchSize / 1.5));\n } else {\n log.error(`${message} ${noSubmission}`);\n this.removeEvents(events);\n }\n\n return;\n }\n\n if (!response.success) {\n log.error(`Error submitting events: ${response.message || 'Please check the network tab for more info.'}`);\n this.suspendProcessing();\n }\n }\n\n private ensureQueueTimer(): void {\n if (!this._queueTimer) {\n this._queueTimer = setInterval(() => this.onProcessQueue(), 10000);\n }\n }\n\n private onProcessQueue(): void {\n if (!this.isQueueProcessingSuspended() && !this._processingQueue) {\n this.process();\n }\n }\n\n public suspendProcessing(durationInMinutes?:number, discardFutureQueuedItems?:boolean, clearQueue?:boolean): void {\n var config:Configuration = this._config; // Optimization for minifier.\n\n if (!durationInMinutes || durationInMinutes <= 0) {\n durationInMinutes = 5;\n }\n\n config.log.info(`Suspending processing for ${durationInMinutes} minutes.`);\n this._suspendProcessingUntil = new Date(new Date().getTime() + (durationInMinutes * 60000));\n\n if (discardFutureQueuedItems) {\n this._discardQueuedItemsUntil = new Date(new Date().getTime() + (durationInMinutes * 60000));\n }\n\n if (clearQueue) {\n // Account is over the limit and we want to ensure that the sample size being sent in will contain newer errors.\n this.removeEvents(config.storage.getList('ex-q'));\n }\n }\n\n private removeEvents(events:{ path:string, value:IEvent }[]) {\n for (var index = 0; index < (events || []).length; index++) {\n this._config.storage.remove(events[index].path);\n }\n }\n\n private isQueueProcessingSuspended(): boolean {\n return this._suspendProcessingUntil && this._suspendProcessingUntil > new Date();\n }\n\n private areQueuedItemsDiscarded(): boolean {\n return this._discardQueuedItemsUntil && this._discardQueuedItemsUntil > new Date();\n }\n}\n\n \n\nexport class InMemoryStorage implements IStorage {\n private _items:IStorageItem[] = [];\n private _maxItems:number;\n\n constructor(maxItems?:number) {\n this._maxItems = maxItems > 0 ? maxItems : 250;\n }\n\n public save(path:string, value:T):boolean {\n if (!path || !value) {\n return false;\n }\n\n this.remove(path);\n if (this._items.push({ created: new Date().getTime(), path: path, value: value }) > this._maxItems) {\n this._items.shift();\n }\n\n return true;\n }\n\n public get(path:string):T {\n var item:IStorageItem = path ? this.getList(`^${path}$`, 1)[0] : null;\n return item ? item.value : null;\n }\n\n public getList(searchPattern?:string, limit?:number):IStorageItem[] {\n var items = this._items; // Optimization for minifier\n if (!searchPattern) {\n return items.slice(0, limit);\n }\n\n var regex = new RegExp(searchPattern);\n var results:IStorageItem[] = [];\n for (var index = 0; index < items.length; index++) {\n if (regex.test(items[index].path)) {\n results.push(items[index]);\n\n if (results.length >= limit) {\n break;\n }\n }\n }\n\n return results;\n }\n\n public remove(path:string):void {\n if (path) {\n var item = this.getList(`^${path}$`, 1)[0];\n if (item) {\n this._items.splice(this._items.indexOf(item), 1);\n }\n }\n }\n}\n\n \n\ndeclare var XDomainRequest:{ new (); create(); };\n\nexport class DefaultSubmissionClient implements ISubmissionClient {\n public configurationVersionHeader:string = 'x-exceptionless-configversion';\n\n public postEvents(events:IEvent[], config:Configuration, callback:(response:SubmissionResponse) => void, isAppExiting?:boolean):void {\n var data = Utils.stringify(events, config.dataExclusions);\n var request = this.createRequest(config, 'POST', '/api/v2/events', data);\n var cb = this.createSubmissionCallback(config, callback);\n\n return config.submissionAdapter.sendRequest(request, cb, isAppExiting);\n }\n\n public postUserDescription(referenceId:string, description:IUserDescription, config:Configuration, callback:(response:SubmissionResponse) => void):void {\n var path = `/api/v2/events/by-ref/${encodeURIComponent(referenceId)}/user-description`;\n var data = Utils.stringify(description, config.dataExclusions);\n var request = this.createRequest(config, 'POST', path, data);\n var cb = this.createSubmissionCallback(config, callback);\n\n return config.submissionAdapter.sendRequest(request, cb);\n }\n\n public getSettings(config:Configuration, callback:(response:SettingsResponse) => void):void {\n var request = this.createRequest(config, 'GET', '/api/v2/projects/config');\n var cb = (status, message, data?, headers?) => {\n if (status !== 200) {\n return callback(new SettingsResponse(false, null, -1, null, message));\n }\n\n var settings:IClientConfiguration;\n try {\n settings = JSON.parse(data);\n } catch (e) {\n config.log.error(`Unable to parse settings: '${data}'`);\n }\n\n if (!settings || isNaN(settings.version)) {\n return callback(new SettingsResponse(false, null, -1, null, 'Invalid configuration settings.'));\n }\n\n callback(new SettingsResponse(true, settings.settings || {}, settings.version));\n }\n\n return config.submissionAdapter.sendRequest(request, cb);\n }\n\n private createRequest(config: Configuration, method: string, path: string, data: string = null): SubmissionRequest {\n return {\n method,\n path,\n data,\n serverUrl: config.serverUrl,\n apiKey: config.apiKey,\n userAgent: config.userAgent\n };\n }\n\n private createSubmissionCallback(config:Configuration, callback:(response:SubmissionResponse) => void) {\n return (status, message, data?, headers?) => {\n var settingsVersion: number = headers && parseInt(headers[this.configurationVersionHeader]);\n SettingsManager.checkVersion(settingsVersion, config);\n\n callback(new SubmissionResponse(status, message));\n };\n }\n}\n\n \n\nexport class Utils {\n public static addRange(target:T[], ...values:T[]) {\n if (!target) {\n target = [];\n }\n\n if (!values || values.length === 0) {\n return target;\n }\n\n for (var index = 0; index < values.length; index++) {\n if (values[index] && target.indexOf(values[index]) < 0) {\n target.push(values[index]);\n }\n }\n\n return target;\n }\n\n public static getHashCode(source:string): string {\n if (!source || source.length === 0) {\n return null;\n }\n\n var hash:number = 0;\n for (var index = 0; index < source.length; index++) {\n var character = source.charCodeAt(index);\n hash = ((hash << 5) - hash) + character;\n hash |= 0;\n }\n\n return hash.toString();\n }\n\n public static getCookies(cookies:string): Object {\n var result:Object = {};\n\n var parts:string[] = (cookies || '').split('; ');\n for (var index = 0; index < parts.length; index++) {\n var cookie:string[] = parts[index].split('=');\n result[cookie[0]] = cookie[1];\n }\n\n return result;\n }\n\n public static guid(): string {\n function s4() {\n return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);\n }\n\n return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();\n }\n\n public static merge(defaultValues:Object, values:Object) {\n var result:Object = {};\n\n for (var key in defaultValues || {}) {\n if (!!defaultValues[key]) {\n result[key] = defaultValues[key];\n }\n }\n\n for (var key in values || {}) {\n if (!!values[key]) {\n result[key] = values[key];\n }\n }\n\n return result;\n }\n\n public static parseVersion(source:string): string {\n if (!source) {\n return null;\n }\n\n var versionRegex = /(v?((\\d+)\\.(\\d+)(\\.(\\d+))?)(?:-([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?(?:\\+([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?)/;\n var matches = versionRegex.exec(source);\n if (matches && matches.length > 0) {\n return matches[0];\n }\n\n return null;\n }\n\n public static parseQueryString(query:string) {\n if (!query || query.length === 0) {\n return null;\n }\n\n var pairs:string[] = query.split('&');\n if (pairs.length === 0) {\n return null;\n }\n\n var result:Object = {};\n for (var index = 0; index < pairs.length; index++) {\n var pair = pairs[index].split('=');\n result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);\n }\n\n return result;\n }\n\n public static randomNumber(): number {\n return Math.floor(Math.random() * 9007199254740992);\n }\n\n public static stringify(data:any, exclusions?:string[]): string {\n function checkForMatch(pattern:string, value:string): boolean {\n if (!pattern || !value || typeof value !== 'string') {\n return false;\n }\n\n var trim = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;\n pattern = pattern.toLowerCase().replace(trim, '');\n value = value.toLowerCase().replace(trim, '');\n\n if (pattern.length <= 0) {\n return false;\n }\n\n var startsWithWildcard:boolean = pattern[0] === '*';\n if (startsWithWildcard) {\n pattern = pattern.slice(1);\n }\n\n var endsWithWildcard:boolean = pattern[pattern.length - 1] === '*';\n if (endsWithWildcard) {\n pattern = pattern.substring(0, pattern.length - 1);\n }\n\n if (startsWithWildcard && endsWithWildcard)\n return value.indexOf(pattern) !== -1;\n\n if (startsWithWildcard)\n return value.lastIndexOf(pattern) === (value.length - pattern.length);\n\n if (endsWithWildcard)\n return value.indexOf(pattern) === 0;\n\n return value === pattern;\n }\n\n function stringifyImpl(data:any, exclusions:string[]): string {\n var cache:string[] = [];\n return JSON.stringify(data, function(key:string, value:any) {\n for (var index = 0; index < (exclusions || []).length; index++) {\n if (checkForMatch(exclusions[index], key)){\n return;\n }\n }\n\n if (typeof value === 'object' && !!value) {\n if (cache.indexOf(value) !== -1) {\n // Circular reference found, discard key\n return;\n }\n\n cache.push(value);\n }\n\n return value;\n });\n }\n\n if (({}).toString.call(data) === '[object Array]') {\n var result = [];\n for (var index = 0; index < data.length; index++) {\n result[index] = JSON.parse(stringifyImpl(data[index], exclusions || []));\n }\n\n return JSON.stringify(result);\n }\n\n return stringifyImpl(data, exclusions || []);\n }\n}\n\n \n\nexport class Configuration implements IConfigurationSettings {\n /**\n * A default list of tags that will automatically be added to every\n * report submitted to the server.\n *\n * @type {Array}\n */\n public defaultTags:string[] = [];\n\n /**\n * A default list of of extended data objects that will automatically\n * be added to every report submitted to the server.\n *\n * @type {{}}\n */\n public defaultData:Object = {};\n\n /**\n * Whether the client is currently enabled or not. If it is disabled,\n * submitted errors will be discarded and no data will be sent to the server.\n *\n * @returns {boolean}\n */\n public enabled:boolean = true;\n\n public environmentInfoCollector:IEnvironmentInfoCollector;\n public errorParser:IErrorParser;\n public lastReferenceIdManager:ILastReferenceIdManager = new DefaultLastReferenceIdManager();\n public log:ILog;\n public moduleCollector:IModuleCollector;\n public requestInfoCollector:IRequestInfoCollector;\n\n /**\n * Maximum number of events that should be sent to the server together in a batch. (Defaults to 50)\n */\n public submissionBatchSize:number;\n public submissionAdapter:ISubmissionAdapter;\n public submissionClient:ISubmissionClient;\n\n /**\n * Contains a dictionary of custom settings that can be used to control\n * the client and will be automatically updated from the server.\n */\n public settings:Object = {};\n\n public storage:IStorage;\n\n public queue:IEventQueue;\n\n constructor(configSettings?:IConfigurationSettings) {\n function inject(fn:any) {\n return typeof fn === 'function' ? fn(this) : fn;\n }\n\n configSettings = Utils.merge(Configuration.defaults, configSettings);\n\n this.log = inject(configSettings.log) || new NullLog();\n this.apiKey = configSettings.apiKey;\n this.serverUrl = configSettings.serverUrl;\n\n this.environmentInfoCollector = inject(configSettings.environmentInfoCollector);\n this.errorParser = inject(configSettings.errorParser);\n this.lastReferenceIdManager = inject(configSettings.lastReferenceIdManager) || new DefaultLastReferenceIdManager();\n this.moduleCollector = inject(configSettings.moduleCollector);\n this.requestInfoCollector = inject(configSettings.requestInfoCollector);\n this.submissionBatchSize = inject(configSettings.submissionBatchSize) || 50;\n this.submissionAdapter = inject(configSettings.submissionAdapter);\n this.submissionClient = inject(configSettings.submissionClient) || new DefaultSubmissionClient();\n this.storage = inject(configSettings.storage) || new InMemoryStorage();\n this.queue = inject(configSettings.queue) || new DefaultEventQueue(this);\n\n SettingsManager.applySavedServerSettings(this);\n EventPluginManager.addDefaultPlugins(this);\n }\n\n /**\n * The API key that will be used when sending events to the server.\n * @type {string}\n * @private\n */\n private _apiKey:string;\n\n /**\n * The API key that will be used when sending events to the server.\n * @returns {string}\n */\n public get apiKey():string {\n return this._apiKey;\n }\n\n /**\n * The API key that will be used when sending events to the server.\n * @param value\n */\n public set apiKey(value:string) {\n this._apiKey = value || null;\n this.log.info(`apiKey: ${this._apiKey}`);\n }\n\n /**\n * Returns true if the apiKey is valid.\n * @returns {boolean}\n */\n public get isValid():boolean {\n return !!this.apiKey && this.apiKey.length >= 10;\n }\n\n /**\n * The server url that all events will be sent to.\n * @type {string}\n * @private\n */\n private _serverUrl:string = 'https://collector.exceptionless.io';\n\n /**\n * The server url that all events will be sent to.\n * @returns {string}\n */\n public get serverUrl():string {\n return this._serverUrl;\n }\n\n /**\n * The server url that all events will be sent to.\n * @param value\n */\n public set serverUrl(value:string) {\n if (!!value) {\n this._serverUrl = value;\n this.log.info(`serverUrl: ${this._serverUrl}`);\n }\n }\n\n /**\n * A list of exclusion patterns.\n * @type {Array}\n * @private\n */\n private _dataExclusions:string[] = [];\n\n /**\n * A list of exclusion patterns that will automatically remove any data that\n * matches them from any data submitted to the server.\n *\n * For example, entering CreditCard will remove any extended data properties,\n * form fields, cookies and query parameters from the report.\n *\n * @returns {string[]}\n */\n public get dataExclusions():string[] {\n var exclusions:string = this.settings['@@DataExclusions'];\n return this._dataExclusions.concat(exclusions && exclusions.split(',') || []);\n }\n\n /**\n * Add items to the list of exclusion patterns that will automatically remove any\n * data that matches them from any data submitted to the server.\n *\n * For example, entering CreditCard will remove any extended data properties, form\n * fields, cookies and query parameters from the report.\n *\n * @param exclusions\n */\n public addDataExclusions(...exclusions:string[]) {\n this._dataExclusions = Utils.addRange(this._dataExclusions, ...exclusions);\n }\n\n /**\n * The list of plugins that will be used in this configuration.\n * @type {Array}\n * @private\n */\n private _plugins:IEventPlugin[] = [];\n\n /**\n * The list of plugins that will be used in this configuration.\n * @returns {IEventPlugin[]}\n */\n public get plugins():IEventPlugin[] {\n return this._plugins.sort((p1:IEventPlugin, p2:IEventPlugin) => {\n return (p1.priority < p2.priority) ? -1 : (p1.priority > p2.priority) ? 1 : 0;\n });\n }\n\n /**\n * Register an plugin to be used in this configuration.\n * @param plugin\n */\n public addPlugin(plugin:IEventPlugin): void;\n\n /**\n * Register an plugin to be used in this configuration.\n * @param name The name used to identify the plugin.\n * @param priority Used to determine plugins priority.\n * @param pluginAction A function that is run.\n */\n public addPlugin(name:string, priority:number, pluginAction:(context:EventPluginContext, next?:() => void) => void): void;\n public addPlugin(pluginOrName:IEventPlugin|string, priority?:number, pluginAction?:(context:EventPluginContext, next?:() => void) => void): void {\n var plugin:IEventPlugin = !!pluginAction ? { name: pluginOrName, priority: priority, run: pluginAction } : pluginOrName;\n if (!plugin || !plugin.run) {\n this.log.error('Add plugin failed: Run method not defined');\n return;\n }\n\n if (!plugin.name) {\n plugin.name = Utils.guid();\n }\n\n if (!plugin.priority) {\n plugin.priority = 0;\n }\n\n var pluginExists:boolean = false;\n var plugins = this._plugins; // optimization for minifier.\n for (var index = 0; index < plugins.length; index++) {\n if (plugins[index].name === plugin.name) {\n pluginExists = true;\n break;\n }\n }\n\n if (!pluginExists) {\n plugins.push(plugin);\n }\n }\n\n /**\n * Remove the plugin from this configuration.\n * @param plugin\n */\n public removePlugin(plugin:IEventPlugin): void;\n\n /**\n * Remove an plugin by key from this configuration.\n * @param name\n */\n public removePlugin(name:string): void;\n public removePlugin(pluginOrName:IEventPlugin|string): void {\n var name:string = typeof pluginOrName === 'string' ? pluginOrName : pluginOrName.name;\n if (!name) {\n this.log.error('Remove plugin failed: Plugin name not defined');\n return;\n }\n\n var plugins = this._plugins; // optimization for minifier.\n for (var index = 0; index < plugins.length; index++) {\n if (plugins[index].name === name) {\n plugins.splice(index, 1);\n break;\n }\n }\n }\n\n /**\n * Automatically set the application version for events.\n * @param version\n */\n public setVersion(version:string): void {\n if (!!version) {\n this.defaultData['@version'] = version;\n }\n }\n\n public setUserIdentity(userInfo:IUserInfo): void;\n public setUserIdentity(identity:string): void;\n public setUserIdentity(identity:string, name:string): void;\n public setUserIdentity(userInfoOrIdentity:IUserInfo|string, name?:string): void {\n const USER_KEY:string = '@user'; // optimization for minifier.\n var userInfo:IUserInfo = typeof userInfoOrIdentity !== 'string' ? userInfoOrIdentity : { identity: userInfoOrIdentity, name: name };\n\n var shouldRemove:boolean = !userInfo || (!userInfo.identity && !userInfo.name);\n if (shouldRemove) {\n delete this.defaultData[USER_KEY];\n } else {\n this.defaultData[USER_KEY] = userInfo;\n }\n\n this.log.info(`user identity: ${shouldRemove ? 'null' : userInfo.identity}`);\n }\n\n /**\n * Used to identify the client that sent the events to the server.\n * @returns {string}\n */\n public get userAgent():string {\n return 'exceptionless-js/1.0.0.0';\n }\n\n /**\n * Automatically set a reference id for error events.\n */\n public useReferenceIds(): void {\n this.addPlugin(new ReferenceIdPlugin());\n }\n\n // TODO: Support a min log level.\n public useDebugLogger(): void {\n this.log = new ConsoleLog();\n }\n\n /**\n * The default configuration settings that are applied to new configuration instances.\n * @type {IConfigurationSettings}\n * @private\n */\n private static _defaultSettings:IConfigurationSettings = null;\n\n /**\n * The default configuration settings that are applied to new configuration instances.\n * @returns {IConfigurationSettings}\n */\n public static get defaults() {\n if (Configuration._defaultSettings === null) {\n Configuration._defaultSettings = {};\n }\n\n return Configuration._defaultSettings;\n }\n}\n\n \n\nexport class EventBuilder {\n private _validIdentifierErrorMessage:string = \"must contain between 8 and 100 alphanumeric or '-' characters.\"; // optimization for minifier.\n\n public target:IEvent;\n public client:ExceptionlessClient;\n public pluginContextData:ContextData;\n\n constructor(event:IEvent, client:ExceptionlessClient, pluginContextData?:ContextData) {\n this.target = event;\n this.client = client;\n this.pluginContextData = pluginContextData || new ContextData();\n }\n\n public setType(type:string): EventBuilder {\n if (!!type) {\n this.target.type = type;\n }\n\n return this;\n }\n\n public setSource(source:string): EventBuilder {\n if (!!source) {\n this.target.source = source;\n }\n\n return this;\n }\n\n public setSessionId(sessionId:string): EventBuilder {\n if (!this.isValidIdentifier(sessionId)) {\n throw new Error(`SessionId ${this._validIdentifierErrorMessage}`);\n }\n\n this.target.session_id = sessionId;\n return this;\n }\n\n public setReferenceId(referenceId:string): EventBuilder {\n if (!this.isValidIdentifier(referenceId)) {\n throw new Error(`ReferenceId ${this._validIdentifierErrorMessage}`);\n }\n\n this.target.reference_id = referenceId;\n return this;\n }\n\n public setMessage(message:string): EventBuilder {\n if (!!message) {\n this.target.message = message;\n }\n\n return this;\n }\n\n public setGeo(latitude: number, longitude: number): EventBuilder {\n if (latitude < -90.0 || latitude > 90.0)\n throw new Error('Must be a valid latitude value between -90.0 and 90.0.');\n if (longitude < -180.0 || longitude > 180.0)\n throw new Error('Must be a valid longitude value between -180.0 and 180.0.');\n\n this.target.geo = `${latitude},${longitude}`;\n return this;\n }\n\n public setUserIdentity(userInfo:IUserInfo): EventBuilder;\n public setUserIdentity(identity:string): EventBuilder;\n public setUserIdentity(identity:string, name:string): EventBuilder;\n public setUserIdentity(userInfoOrIdentity:IUserInfo|string, name?:string): EventBuilder {\n var userInfo = typeof userInfoOrIdentity !== 'string' ? userInfoOrIdentity : { identity: userInfoOrIdentity, name: name };\n if (!userInfo || (!userInfo.identity && !userInfo.name)) {\n return this;\n }\n\n this.setProperty('@user', userInfo);\n return this;\n }\n\n public setValue(value:number): EventBuilder {\n if (!!value) {\n this.target.value = value;\n }\n\n return this;\n }\n\n public addTags(...tags:string[]): EventBuilder {\n this.target.tags = Utils.addRange(this.target.tags, ...tags);\n return this;\n }\n\n public setProperty(name:string, value:any): EventBuilder {\n if (!name || (value === undefined || value == null)) {\n return this;\n }\n\n if (!this.target.data) {\n this.target.data = {};\n }\n\n this.target.data[name] = value;\n return this;\n }\n\n public markAsCritical(critical:boolean): EventBuilder {\n if (critical) {\n this.addTags('Critical');\n }\n\n return this;\n }\n\n public addRequestInfo(request:Object): EventBuilder {\n if (!!request) {\n this.pluginContextData['@request'] = request;\n }\n\n return this;\n }\n\n public submit(callback?:(context:EventPluginContext) => void): void {\n this.client.submitEvent(this.target, this.pluginContextData, callback);\n }\n\n private isValidIdentifier(value:string): boolean {\n if (!value) {\n return true;\n }\n\n if (value.length < 8 || value.length > 100) {\n return false;\n }\n\n for (var index = 0; index < value.length; index++) {\n var code = value.charCodeAt(index);\n var isDigit = (code >= 48) && (code <= 57);\n var isLetter = ((code >= 65) && (code <= 90)) || ((code >= 97) && (code <= 122));\n var isMinus = code === 45;\n\n if (!(isDigit || isLetter) && !isMinus) {\n return false;\n }\n }\n\n return true;\n }\n}\n\n \n\nexport interface IError extends IInnerError {\n modules?:IModule[]\n}\n\nexport interface IUserDescription {\n email_address?:string;\n description?:string;\n data?:any;\n}\n\nexport class ContextData {\n public setException(exception:Error): void {\n if (exception) {\n this['@@_Exception'] = exception;\n }\n }\n\n public get hasException(): boolean {\n return !!this['@@_Exception']\n }\n\n public getException(): Error {\n return this['@@_Exception'] || null;\n }\n\n public markAsUnhandledError(): void {\n this['@@_IsUnhandledError'] = true;\n }\n\n public get isUnhandledError(): boolean {\n return !!this['@@_IsUnhandledError'];\n }\n\n public setSubmissionMethod(method:string): void {\n if (method) {\n this['@@_SubmissionMethod'] = method;\n }\n }\n\n public getSubmissionMethod(): string {\n return this['@@_SubmissionMethod'] || null;\n }\n}\n\nexport class SubmissionResponse {\n success:boolean = false;\n badRequest:boolean = false;\n serviceUnavailable:boolean = false;\n paymentRequired:boolean = false;\n unableToAuthenticate:boolean = false;\n notFound:boolean = false;\n requestEntityTooLarge:boolean = false;\n statusCode:number;\n message:string;\n\n constructor(statusCode:number, message?:string) {\n this.statusCode = statusCode;\n this.message = message;\n\n this.success = statusCode >= 200 && statusCode <= 299;\n this.badRequest = statusCode === 400;\n this.serviceUnavailable = statusCode === 503;\n this.paymentRequired = statusCode === 402;\n this.unableToAuthenticate = statusCode === 401 || statusCode === 403;\n this.notFound = statusCode === 404;\n this.requestEntityTooLarge = statusCode === 413;\n }\n}\n\n \n\nexport class ExceptionlessClient {\n public config:Configuration;\n\n constructor();\n constructor(settings:IConfigurationSettings);\n constructor(apiKey:string, serverUrl?:string);\n constructor(settingsOrApiKey?:IConfigurationSettings|string, serverUrl?:string) {\n if (typeof settingsOrApiKey !== 'object') {\n this.config = new Configuration(settingsOrApiKey);\n } else {\n this.config = new Configuration({ apiKey: settingsOrApiKey, serverUrl: serverUrl });\n }\n }\n\n public createException(exception:Error): EventBuilder {\n var pluginContextData = new ContextData();\n pluginContextData.setException(exception);\n return this.createEvent(pluginContextData).setType('error');\n }\n\n public submitException(exception:Error, callback?:(context:EventPluginContext) => void): void {\n this.createException(exception).submit(callback);\n }\n\n public createUnhandledException(exception:Error, submissionMethod?:string): EventBuilder {\n var builder = this.createException(exception);\n builder.pluginContextData.markAsUnhandledError();\n builder.pluginContextData.setSubmissionMethod(submissionMethod);\n\n return builder;\n }\n\n public submitUnhandledException(exception:Error, submissionMethod?:string, callback?:(context:EventPluginContext) => void) {\n this.createUnhandledException(exception, submissionMethod).submit(callback);\n }\n\n public createFeatureUsage(feature:string): EventBuilder {\n return this.createEvent().setType('usage').setSource(feature);\n }\n\n public submitFeatureUsage(feature:string, callback?:(context:EventPluginContext) => void): void {\n this.createFeatureUsage(feature).submit(callback);\n }\n\n public createLog(message:string): EventBuilder;\n public createLog(source:string, message:string): EventBuilder;\n public createLog(source:string, message:string, level:string): EventBuilder;\n public createLog(sourceOrMessage:string, message?:string, level?:string): EventBuilder {\n var builder = this.createEvent().setType('log');\n\n if (message && level) {\n builder = builder.setSource(sourceOrMessage).setMessage(message).setProperty('@level', level);\n } else if (message) {\n builder = builder.setSource(sourceOrMessage).setMessage(message);\n } else {\n // TODO: Look into using https://www.stevefenton.co.uk/Content/Blog/Date/201304/Blog/Obtaining-A-Class-Name-At-Runtime-In-TypeScript/\n var caller:any = arguments.callee.caller;\n builder = builder.setSource(caller && caller.name).setMessage(sourceOrMessage);\n }\n\n return builder;\n }\n\n public submitLog(message:string): void;\n public submitLog(source:string, message:string): void;\n public submitLog(source:string, message:string, level:string, callback?:(context:EventPluginContext) => void): void;\n public submitLog(sourceOrMessage:string, message?:string, level?:string, callback?:(context:EventPluginContext) => void): void {\n this.createLog(sourceOrMessage, message, level).submit(callback);\n }\n\n public createNotFound(resource:string): EventBuilder {\n return this.createEvent().setType('404').setSource(resource);\n }\n\n public submitNotFound(resource:string, callback?:(context:EventPluginContext) => void): void {\n this.createNotFound(resource).submit(callback);\n }\n\n public createSessionStart(sessionId:string): EventBuilder {\n return this.createEvent().setType('start').setSessionId(sessionId);\n }\n\n public submitSessionStart(sessionId:string, callback?:(context:EventPluginContext) => void): void {\n this.createSessionStart(sessionId).submit(callback);\n }\n\n public createSessionEnd(sessionId:string): EventBuilder {\n return this.createEvent().setType('end').setSessionId(sessionId);\n }\n\n public submitSessionEnd(sessionId:string, callback?:(context:EventPluginContext) => void): void {\n this.createSessionEnd(sessionId).submit(callback);\n }\n\n public createEvent(pluginContextData?:ContextData): EventBuilder {\n return new EventBuilder({ date: new Date() }, this, pluginContextData);\n }\n\n /**\n * Submits the event to be sent to the server.\n * @param event The event data.\n * @param pluginContextData Any contextual data objects to be used by Exceptionless plugins to gather default information for inclusion in the report information.\n * @param callback\n */\n public submitEvent(event:IEvent, pluginContextData?:ContextData, callback?:(context:EventPluginContext) => void): void {\n function cancelled() {\n if (!!context) {\n context.cancelled = true;\n }\n\n return !!callback && callback(context);\n }\n\n if (!event) {\n return cancelled();\n }\n\n if (!this.config.enabled) {\n this.config.log.info('Event submission is currently disabled.');\n return cancelled();\n }\n\n if (!event.data) {\n event.data = {};\n }\n\n if (!event.tags || !event.tags.length) {\n event.tags = [];\n }\n\n var context = new EventPluginContext(this, event, pluginContextData);\n EventPluginManager.run(context, function (context:EventPluginContext) {\n let ev = context.event;\n if (!context.cancelled) {\n // ensure all required data\n if (!ev.type || ev.type.length === 0) {\n ev.type = 'log';\n }\n\n if (!ev.date) {\n ev.date = new Date();\n }\n\n var config = context.client.config;\n config.queue.enqueue(ev);\n\n if (ev.reference_id && ev.reference_id.length > 0) {\n context.log.info(`Setting last reference id '${ev.reference_id}'`);\n config.lastReferenceIdManager.setLast(ev.reference_id);\n }\n }\n\n !!callback && callback(context);\n });\n }\n\n /**\n * Updates the user's email address and description of an event for the specified reference id.\n * @param referenceId The reference id of the event to update.\n * @param email The user's email address to set on the event.\n * @param description The user's description of the event.\n */\n public updateUserEmailAndDescription(referenceId:string, email:string, description:string, callback?:(response:SubmissionResponse) => void) {\n if (!referenceId || !email || !description || !this.config.enabled) {\n return !!callback && callback(new SubmissionResponse(500, 'cancelled'));\n }\n\n var userDescription:IUserDescription = { email_address: email, description: description };\n var response = this.config.submissionClient.postUserDescription(referenceId, userDescription, this.config, (response:SubmissionResponse) => {\n if (!response.success) {\n this.config.log.error(`Failed to submit user email and description for event '${referenceId}': ${response.statusCode} ${response.message}`)\n }\n\n !!callback && callback(response);\n });\n }\n\n /**\n * Gets the last event client id that was submitted to the server.\n * @returns {string} The event client id.\n */\n public getLastReferenceId(): string {\n return this.config.lastReferenceIdManager.getLast();\n }\n\n /**\n * The default ExceptionlessClient instance.\n * @type {ExceptionlessClient}\n * @private\n */\n private static _instance:ExceptionlessClient = null;\n\n\n /**\n * The default ExceptionlessClient instance.\n * @type {ExceptionlessClient}\n */\n public static get default() {\n if(ExceptionlessClient._instance === null) {\n ExceptionlessClient._instance = new ExceptionlessClient(null);\n }\n\n return ExceptionlessClient._instance;\n }\n}\n\nexport interface IParameter {\n data?:any;\n generic_arguments?:string[];\n\n name?:string;\n type?:string;\n type_namespace?:string;\n}\n\n \n\nexport interface IMethod {\n data?:any;\n generic_arguments?:string[];\n parameters?:IParameter[];\n\n is_signature_target?:boolean;\n declaring_namespace?:string;\n declaring_type?:string;\n name?:string;\n module_id?:number;\n}\n\n \n\nexport interface IStackFrame extends IMethod {\n file_name?:string;\n line_number?:number;\n column?:number;\n}\n\n \n\nexport interface IInnerError {\n message?:string;\n type?:string;\n code?:string;\n data?:any;\n inner?:IInnerError\n stack_trace?:IStackFrame[];\n target_method?:IMethod;\n}\n\nexport interface IModule {\n data?:any;\n\n module_id?:number;\n name?:string;\n version?:string;\n is_entry?:boolean;\n created_date?:Date;\n modified_date?:Date;\n}\n\nexport interface IRequestInfo {\n user_agent?:string;\n http_method?:string;\n is_secure?:boolean;\n host?:string;\n port?:number;\n path?:string;\n referrer?:string;\n client_ip_address?:string;\n cookies?:any;\n post_data?:any;\n query_string?:any;\n data?:any\n}\n\nexport interface IEnvironmentInfo {\n processor_count?:number;\n total_physical_memory?:number;\n available_physical_memory?:number;\n command_line?:string;\n process_name?:string;\n process_id?:string;\n process_memory_size?:number;\n thread_id?:string;\n architecture?:string;\n o_s_name?:string;\n o_s_version?:string;\n ip_address?:string;\n machine_name?:string;\n install_id?:string;\n runtime_version?:string;\n data?:any;\n}\n\n \n\nexport class ConfigurationDefaultsPlugin implements IEventPlugin {\n public priority:number = 10;\n public name:string = 'ConfigurationDefaultsPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n var defaultTags:string[] = context.client.config.defaultTags || [];\n for (var index = 0; index < defaultTags.length; index++) {\n var tag = defaultTags[index];\n if (!!tag && context.event.tags.indexOf(tag) < 0) {\n context.event.tags.push(tag)\n }\n }\n\n var defaultData:Object = context.client.config.defaultData || {};\n for (var key in defaultData) {\n if (!!defaultData[key]) {\n context.event.data[key] = defaultData[key];\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class ErrorPlugin implements IEventPlugin {\n public priority:number = 30;\n public name:string = 'ErrorPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ERROR_KEY:string = '@error'; // optimization for minifier.\n\n var exception = context.contextData.getException();\n if (!!exception) {\n context.event.type = 'error';\n\n if (!context.event.data[ERROR_KEY]) {\n var parser = context.client.config.errorParser;\n if (!parser) {\n throw new Error('No error parser was defined.');\n }\n\n var result = parser.parse(context, exception);\n if (!!result) {\n context.event.data[ERROR_KEY] = result;\n }\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class ModuleInfoPlugin implements IEventPlugin {\n public priority:number = 40;\n public name:string = 'ModuleInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ERROR_KEY:string = '@error'; // optimization for minifier.\n\n var collector = context.client.config.moduleCollector;\n if (context.event.data[ERROR_KEY] && !context.event.data['@error'].modules && !!collector) {\n var modules = collector.getModules(context);\n if (modules && modules.length > 0) {\n context.event.data[ERROR_KEY].modules = modules;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class RequestInfoPlugin implements IEventPlugin {\n public priority:number = 60;\n public name:string = 'RequestInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const REQUEST_KEY:string = '@request'; // optimization for minifier.\n\n var collector = context.client.config.requestInfoCollector;\n if (!context.event.data[REQUEST_KEY] && !!collector) {\n var requestInfo = collector.getRequestInfo(context);\n if (!!requestInfo) {\n context.event.data[REQUEST_KEY] = requestInfo;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class EnvironmentInfoPlugin implements IEventPlugin {\n public priority:number = 70;\n public name:string = 'EnvironmentInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ENVIRONMENT_KEY:string = '@environment'; // optimization for minifier.\n\n var collector = context.client.config.environmentInfoCollector;\n if (!context.event.data[ENVIRONMENT_KEY] && collector) {\n var environmentInfo = collector.getEnvironmentInfo(context);\n if (!!environmentInfo) {\n context.event.data[ENVIRONMENT_KEY] = environmentInfo;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class SubmissionMethodPlugin implements IEventPlugin {\n public priority:number = 100;\n public name:string = 'SubmissionMethodPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n var submissionMethod:string = context.contextData.getSubmissionMethod();\n if (!!submissionMethod) {\n context.event.data['@submission_method'] = submissionMethod;\n }\n\n next && next();\n }\n}\n\nexport interface IStorageItem {\n created:number;\n path:string;\n value:T;\n}\n\nexport interface SubmissionCallback {\n (status: number, message: string, data?: string, headers?: Object): void\n}\n\nexport interface SubmissionRequest {\n serverUrl: string;\n apiKey: string;\n userAgent: string;\n method: string;\n path: string;\n data: string;\n}\n\nexport class SettingsResponse {\n success:boolean = false;\n settings:any;\n settingsVersion:number = -1;\n message:string;\n exception:any;\n\n constructor(success:boolean, settings:any, settingsVersion:number = -1, exception:any = null, message:string = null) {\n this.success = success;\n this.settings = settings;\n this.settingsVersion = settingsVersion;\n this.exception = exception;\n this.message = message;\n }\n}\n\nexport interface IClientConfiguration {\n settings:Object;\n version:number;\n}\n\nimport os = require('os');\nimport nodestacktrace = require('stack-trace');\nimport https = require('https');\nimport url = require('url');\n \n\nexport class NodeEnvironmentInfoCollector implements IEnvironmentInfoCollector {\n public getEnvironmentInfo(context:EventPluginContext): IEnvironmentInfo {\n function getIpAddresses():string {\n var ips:string[] = [];\n var interfaces = os.networkInterfaces();\n Object.keys(interfaces).forEach((name) => {\n interfaces[name].forEach((iface:any) => {\n if ('IPv4' === iface.family && !iface.internal) {\n ips.push(iface.address);\n }\n });\n });\n\n return ips.join(', ');\n }\n\n if (!os) {\n return null;\n }\n \n var environmentInfo: IEnvironmentInfo = {\n processor_count: os.cpus().length,\n total_physical_memory: os.totalmem(),\n available_physical_memory: os.freemem(),\n command_line: process.argv.join(' '),\n process_name: (process.title || '').replace(/[\\uE000-\\uF8FF]/g, ''),\n process_id: process.pid + '',\n process_memory_size: process.memoryUsage().heapTotal,\n //thread_id: '',\n architecture: os.arch(),\n o_s_name: os.type(),\n o_s_version: os.release(),\n ip_address: getIpAddresses(),\n machine_name: os.hostname(),\n //install_id: '',\n runtime_version: process.version,\n data: {\n loadavg: os.loadavg(),\n platform: os.platform(),\n tmpdir: os.tmpdir(),\n uptime: os.uptime()\n }\n };\n\n if ((os).endianness) {\n environmentInfo.data.endianness = (os).endianness();\n }\n\n return environmentInfo;\n }\n}\n\n \n\nexport class NodeErrorParser implements IErrorParser {\n public parse(context:EventPluginContext, exception:Error): IError {\n function getStackFrames(context:EventPluginContext, stackFrames:any[]): IStackFrame[] {\n var frames:IStackFrame[] = [];\n\n for (var index = 0; index < stackFrames.length; index++) {\n var frame = stackFrames[index];\n frames.push({\n name: frame.getMethodName() || frame.getFunctionName(),\n //parameters: frame.args,\n file_name: frame.getFileName(),\n line_number: frame.getLineNumber() || 0,\n column: frame.getColumnNumber() || 0,\n declaring_type: frame.getTypeName(),\n data: {\n is_native: frame.isNative() || (!!frame.filename && frame.filename[0] !== '/' && frame.filename[0] !== '.')\n }\n });\n }\n\n return frames;\n }\n\n if (!nodestacktrace) {\n throw new Error('Unable to load the stack trace library.');\n }\n\n var stackFrames = nodestacktrace.parse(exception) || [];\n return {\n type: exception.name,\n message: exception.message,\n stack_trace: getStackFrames(context, stackFrames)\n };\n }\n}\n\n \n\nexport class NodeRequestInfoCollector implements IRequestInfoCollector {\n getRequestInfo(context:EventPluginContext):IRequestInfo {\n const REQUEST_KEY:string = '@request'; // optimization for minifier.\n if (!context.contextData[REQUEST_KEY]) {\n return null;\n }\n\n var request = context.contextData[REQUEST_KEY];\n var requestInfo:IRequestInfo = {\n client_ip_address: request.ip,\n user_agent: request.headers['user-agent'],\n is_secure: request.secure,\n http_method: request.method,\n host: request.hostname || request.host,\n path: request.path,\n post_data: request.body,\n //referrer: TODO,\n cookies: Utils.getCookies((request || {}).headers['cookie']),\n query_string: request.params\n };\n\n var host = request.headers['host'];\n var port:number = host && parseInt(host.slice(host.indexOf(':') + 1));\n if (port > 0) {\n requestInfo.port = port;\n }\n\n return requestInfo;\n }\n}\n\n \n\nexport class NodeSubmissionAdapter implements ISubmissionAdapter {\n public sendRequest(request:SubmissionRequest, callback:SubmissionCallback, isAppExiting?:boolean) {\n if (isAppExiting) {\n this.sendRequestSync(request, callback);\n return;\n }\n\n var parsedHost = url.parse(request.serverUrl);\n\n var options: https.RequestOptions = {\n auth: `client:${request.apiKey}`,\n headers: {},\n hostname: parsedHost.hostname,\n method: request.method,\n port: parsedHost.port && parseInt(parsedHost.port),\n path: request.path\n };\n\n options.headers['User-Agent'] = request.userAgent;\n\n if (request.method === 'POST') {\n options.headers = {\n 'Content-Type': 'application/json',\n 'Content-Length': request.data.length\n }\n }\n\n var protocol = (parsedHost.protocol === 'https' ? https : http);\n var clientRequest: http.ClientRequest = protocol.request(options, (response: http.IncomingMessage) => {\n var body = '';\n response.setEncoding('utf8');\n response.on('data', (chunk) => body += chunk);\n response.on('end', () => this.complete(response, body, response.headers, callback));\n });\n\n clientRequest.on('error', (error: Error) => callback(500, error.message));\n clientRequest.end(request.data);\n }\n\n private complete(response: http.IncomingMessage, responseBody: string, responseHeaders: Object, callback: SubmissionCallback): void {\n var message: string;\n if (response.statusCode === 0) {\n message = 'Unable to connect to server.';\n } else if (response.statusCode < 200 || response.statusCode > 299) {\n message = response.statusMessage || (response).message;\n }\n\n callback(response.statusCode || 500, message, responseBody, responseHeaders);\n }\n\n private sendRequestSync(request: SubmissionRequest, callback: SubmissionCallback): void {\n var requestJson = JSON.stringify(request);\n var res = child.spawnSync(process.execPath, [require.resolve('./submitSync.js')],\n {\n input: requestJson,\n stdio: ['pipe', 'pipe', process.stderr]\n });\n\n var out = res.stdout.toString();\n var result = JSON.parse(out);\n\n callback(result.status, result.message, result.data, result.headers);\n }\n}\n\n \n\nconst EXIT: string = 'exit';\nconst UNCAUGHT_EXCEPTION: string = 'uncaughtException';\nconst SIGINT: string = 'SIGINT';\nconst SIGINT_CODE: number = 2;\n\nvar defaults = Configuration.defaults;\ndefaults.environmentInfoCollector = new NodeEnvironmentInfoCollector();\ndefaults.errorParser = new NodeErrorParser();\ndefaults.requestInfoCollector = new NodeRequestInfoCollector();\ndefaults.submissionAdapter = new NodeSubmissionAdapter();\n\nfunction getListenerCount(emitter, event:string): number {\n if (emitter.listenerCount) {\n return emitter.listenerCount(event);\n }\n return require(\"events\").listenerCount(emitter, event);\n}\n\n/*\n * Adding a event handler for 'uncaughtException' modifies the default\n * Node behavior, so it won't exit or log to the console. Instead,\n * we hijack the event emitter and forward the exception to the callback.\n */\nfunction onUncaughtException(callback: (error: Error) => void) {\n var originalEmit = process.emit;\n\n process.emit = function(type: string, error: Error) {\n if (type === UNCAUGHT_EXCEPTION) {\n callback(error);\n }\n\n return originalEmit.apply(this, arguments);\n }\n}\n\nonUncaughtException(function(error: Error) {\n ExceptionlessClient.default.submitUnhandledException(error, UNCAUGHT_EXCEPTION);\n});\n\n/*\n * We cannot hijack SIGINT, so if there are no other handlers,\n * we just reproduce default Node.js behavior by exiting.\n */\nprocess.on(SIGINT, function() {\n if (getListenerCount(process, SIGINT) <= 1) {\n process.exit(128 + SIGINT_CODE);\n }\n})\n\nprocess.on(EXIT, function(code: number) {\n /**\n * exit codes: https://nodejs.org/api/process.html#process_event_exit\n * From now on, only synchronous code may run. As soon as this method\n * ends, the application inevitably will exit.\n */\n function getExitCodeReason(code: number): string {\n if (code === 1) {\n return 'Uncaught Fatal Exception';\n }\n\n if (code === 3) {\n return 'Internal JavaScript Parse Error';\n }\n\n if (code === 4) {\n return 'Internal JavaScript Evaluation Failure';\n }\n\n if (code === 5) {\n return 'Fatal Exception';\n }\n\n if (code === 6) {\n return 'Non-function Internal Exception Handler ';\n }\n\n if (code === 7) {\n return 'Internal Exception Handler Run-Time Failure';\n }\n\n if (code === 8) {\n return 'Uncaught Exception';\n }\n\n if (code === 9) {\n return 'Invalid Argument';\n }\n\n if (code === 10) {\n return 'Internal JavaScript Run-Time Failure';\n }\n\n if (code === 12) {\n return 'Invalid Debug Argument';\n }\n\n return null;\n }\n\n var client = ExceptionlessClient.default;\n var config = client.config;\n var message = getExitCodeReason(code);\n\n if (message !== null) {\n client.submitLog(EXIT, message, 'Error')\n }\n\n config.queue.process(true);\n\n // Application will now exit.\n});\n\n(Error).stackTraceLimit = Infinity;\n\n"]} \ No newline at end of file +{"version":3,"file":"exceptionless.node.js","sourceRoot":"/source/","sources":["exceptionless.node.ts"],"names":["getListenerCount","onUncaughtException","getExitCodeReason"],"mappings":"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","sourcesContent":["import * as events from \"events\";\nimport * as net from \"net\";\nimport * as stream from \"stream\";\nimport * as child from \"child_process\";\nimport * as tls from \"tls\";\nimport * as http from \"http\";\nimport * as crypto from \"crypto\";\nexport interface IEvent {\n type?:string;\n source?:string;\n date?:Date;\n tags?:string[];\n message?:string;\n geo?:string;\n value?:number;\n data?:any;\n reference_id?:string;\n session_id?:string;\n}\n\nexport interface ILastReferenceIdManager {\n getLast(): string;\n clearLast(): void;\n setLast(eventId:string): void;\n}\n\nexport interface ILog {\n info(message:string):void;\n warn(message:string):void;\n error(message:string):void;\n}\n\n \n\nexport interface IEventQueue {\n enqueue(event:IEvent):void;\n process(isAppExiting?:boolean):void;\n suspendProcessing(durationInMinutes?:number, discardFutureQueuedItems?:boolean, clearQueue?:boolean):void;\n}\n\n \n\nexport interface IEnvironmentInfoCollector {\n getEnvironmentInfo(context:EventPluginContext):IEnvironmentInfo;\n}\n\n \n\nexport interface IErrorParser {\n parse(context:EventPluginContext, exception:Error): IError;\n}\n\n \n\nexport interface IModuleCollector {\n getModules(context:EventPluginContext):IModule[];\n}\n\n \n\nexport interface IRequestInfoCollector {\n getRequestInfo(context:EventPluginContext):IRequestInfo;\n}\n\n \n\nexport interface IStorage {\n save(path:string, value:T):boolean;\n get(path:string):T;\n getList(searchPattern?:string, limit?:number):IStorageItem[];\n remove(path:string):void;\n}\n\n \n\nexport interface ISubmissionAdapter {\n sendRequest(request:SubmissionRequest, callback:SubmissionCallback, isAppExiting?:boolean): void\n}\n\n \n\nexport interface ISubmissionClient {\n postEvents(events:IEvent[], config:Configuration, callback:(response:SubmissionResponse) => void, isAppExiting?:boolean):void;\n postUserDescription(referenceId:string, description:IUserDescription, config:Configuration, callback:(response:SubmissionResponse) => void):void;\n getSettings(config:Configuration, callback:(response:SettingsResponse) => void):void;\n}\n\n \n\nexport interface IConfigurationSettings {\n apiKey?:string;\n serverUrl?:string;\n environmentInfoCollector?:IEnvironmentInfoCollector;\n errorParser?:IErrorParser;\n lastReferenceIdManager?:ILastReferenceIdManager;\n log?:ILog;\n moduleCollector?:IModuleCollector;\n requestInfoCollector?:IRequestInfoCollector;\n submissionBatchSize?:number;\n submissionClient?:ISubmissionClient;\n submissionAdapter?:ISubmissionAdapter;\n storage?:IStorage;\n queue?:IEventQueue;\n}\n\n \n\nexport class SettingsManager {\n /**\n * The configuration settings path.\n * @type {string}\n * @private\n */\n private static _configPath:string = 'ex-server-settings.json';\n\n /**\n * A list of handlers that will be fired when the settings change.\n * @type {Array}\n * @private\n */\n private static _handlers:{ (config:Configuration):void }[] = [];\n\n private static changed(config:Configuration) {\n var handlers = this._handlers; // optimization for minifier.\n for (var index = 0; index < handlers.length; index++) {\n handlers[index](config);\n }\n }\n\n public static onChanged(handler:(config:Configuration) => void) {\n !!handler && this._handlers.push(handler);\n }\n\n public static applySavedServerSettings(config:Configuration):void {\n config.log.info('Applying saved settings.');\n config.settings = Utils.merge(config.settings, this.getSavedServerSettings(config));\n this.changed(config);\n }\n\n private static getSavedServerSettings(config:Configuration):Object {\n return config.storage.get(this._configPath) || {};\n }\n\n public static checkVersion(version:number, config:Configuration):void {\n if (version) {\n var savedConfigVersion = parseInt(config.storage.get(`${this._configPath}-version`));\n if (isNaN(savedConfigVersion) || version > savedConfigVersion) {\n config.log.info(`Updating settings from v${(!isNaN(savedConfigVersion) ? savedConfigVersion : 0)} to v${version}`);\n this.updateSettings(config);\n }\n }\n }\n\n public static updateSettings(config:Configuration):void {\n if (!config.isValid) {\n config.log.error('Unable to update settings: ApiKey is not set.');\n return;\n }\n\n config.submissionClient.getSettings(config, (response:SettingsResponse) => {\n if (!response || !response.success || !response.settings) {\n return;\n }\n\n config.settings = Utils.merge(config.settings, response.settings);\n\n // TODO: Store snapshot of settings after reading from config and attributes and use that to revert to defaults.\n // Remove any existing server settings that are not in the new server settings.\n var savedServerSettings = SettingsManager.getSavedServerSettings(config);\n for (var key in savedServerSettings) {\n if (response.settings[key]) {\n continue;\n }\n\n delete config.settings[key];\n }\n\n var path = SettingsManager._configPath; // optimization for minifier.\n config.storage.save(`${path}-version`, response.settingsVersion);\n config.storage.save(path, response.settings);\n\n config.log.info('Updated settings');\n this.changed(config);\n });\n }\n}\n\n \n\nexport class DefaultLastReferenceIdManager implements ILastReferenceIdManager {\n /**\n * Gets the last event's reference id that was submitted to the server.\n * @type {string}\n * @private\n */\n private _lastReferenceId:string = null;\n\n /**\n * Gets the last event's reference id that was submitted to the server.\n * @returns {string}\n */\n getLast(): string {\n return this._lastReferenceId;\n }\n\n /**\n * Clears the last event's reference id.\n */\n clearLast():void {\n this._lastReferenceId = null;\n }\n\n /**\n * Sets the last event's reference id.\n * @param eventId\n */\n setLast(eventId:string):void {\n this._lastReferenceId = eventId;\n }\n}\n\n \n\nexport class ConsoleLog implements ILog {\n public info(message:string):void {\n this.log('info', message);\n }\n\n public warn(message:string):void {\n this.log('warn', message);\n }\n\n public error(message:string):void {\n this.log('error', message);\n }\n\n private log(level:string, message:string) {\n if (console && console[level]) {\n console[level](`[${level}] Exceptionless: ${message}`);\n }\n }\n}\n\n \n\nexport class NullLog implements ILog {\n public info(message:string):void {}\n public warn(message:string):void {}\n public error(message:string):void {}\n}\n\nexport interface IUserInfo {\n identity?:string;\n name?:string;\n data?:any;\n}\n\n \n\nexport interface IEventPlugin {\n priority?:number;\n name?:string;\n run(context:EventPluginContext, next?:() => void): void;\n}\n\n \n\nexport class EventPluginContext {\n public cancelled:boolean;\n public client:ExceptionlessClient;\n public event:IEvent;\n public contextData:ContextData;\n\n constructor(client:ExceptionlessClient, event:IEvent, contextData?:ContextData) {\n this.client = client;\n this.event = event;\n this.contextData = contextData ? contextData : new ContextData();\n }\n\n public get log(): ILog {\n return this.client.config.log;\n }\n}\n\n \n\nexport class EventPluginManager {\n public static run(context:EventPluginContext, callback:(context?:EventPluginContext) => void): void {\n var wrap = function (plugin:IEventPlugin, next?:() => void): () => void {\n return () => {\n try {\n if (!context.cancelled) {\n plugin.run(context, next);\n }\n } catch (ex) {\n context.cancelled = true;\n context.log.error(`Error running plugin '${plugin.name}': ${ex.message}. Discarding Event.`);\n }\n\n if (context.cancelled && !!callback) {\n callback(context);\n }\n };\n };\n\n var plugins:IEventPlugin[] = context.client.config.plugins; // optimization for minifier.\n var wrappedPlugins:{ (): void }[] = [];\n if (!!callback) {\n wrappedPlugins[plugins.length] = wrap({ name: 'cb', priority: 9007199254740992, run: callback }, null);\n }\n\n for (var index = plugins.length - 1; index > -1; index--) {\n wrappedPlugins[index] = wrap(plugins[index], !!callback || (index < plugins.length - 1) ? wrappedPlugins[index + 1] : null);\n }\n\n wrappedPlugins[0]();\n }\n\n public static addDefaultPlugins(config:Configuration): void {\n config.addPlugin(new ConfigurationDefaultsPlugin());\n config.addPlugin(new ErrorPlugin());\n config.addPlugin(new ModuleInfoPlugin());\n config.addPlugin(new RequestInfoPlugin());\n config.addPlugin(new EnvironmentInfoPlugin());\n config.addPlugin(new SubmissionMethodPlugin());\n }\n}\n\n \n\nexport class ReferenceIdPlugin implements IEventPlugin {\n public priority:number = 20;\n public name:string = 'ReferenceIdPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n if ((!context.event.reference_id || context.event.reference_id.length === 0) && context.event.type === 'error') {\n context.event.reference_id = Utils.guid().replace('-', '').substring(0, 10);\n }\n\n next && next();\n }\n}\n\n \n\nexport class DefaultEventQueue implements IEventQueue {\n /**\n * The configuration object.\n * @type {Configuration}\n * @private\n */\n private _config:Configuration;\n\n /**\n * Suspends processing until the specified time.\n * @type {Date}\n * @private\n */\n private _suspendProcessingUntil:Date;\n\n /**\n * Discards queued items until the specified time.\n * @type {Date}\n * @private\n */\n private _discardQueuedItemsUntil:Date;\n\n /**\n * Returns true if the queue is processing.\n * @type {boolean}\n * @private\n */\n private _processingQueue:boolean = false;\n\n /**\n * Processes the queue every xx seconds.\n * @type {Timer}\n * @private\n */\n private _queueTimer:any;\n\n constructor(config:Configuration) {\n this._config = config;\n }\n\n public enqueue(event:IEvent): void {\n var config:Configuration = this._config; // Optimization for minifier.\n this.ensureQueueTimer();\n\n if (this.areQueuedItemsDiscarded()) {\n config.log.info('Queue items are currently being discarded. The event will not be queued.');\n return;\n }\n\n var key = `ex-q-${new Date().toJSON()}-${Utils.randomNumber()}`;\n config.log.info(`Enqueuing event: ${key} type=${event.type} ${!!event.reference_id ? 'refid=' + event.reference_id : ''}`);\n config.storage.save(key, event);\n }\n\n public process(isAppExiting?:boolean): void {\n function getEvents(events:{ path:string, value:IEvent }[]):IEvent[] {\n var items:IEvent[] = [];\n for (var index = 0; index < events.length; index++) {\n items.push(events[index].value);\n }\n\n return items;\n }\n\n const queueNotProcessed:string = 'The queue will not be processed.'; // optimization for minifier.\n var config:Configuration = this._config; // Optimization for minifier.\n var log:ILog = config.log; // Optimization for minifier.\n\n this.ensureQueueTimer();\n\n if (this._processingQueue) {\n return;\n }\n\n log.info('Processing queue...');\n if (!config.enabled) {\n log.info(`Configuration is disabled. ${queueNotProcessed}`);\n return;\n }\n\n if (!config.isValid) {\n log.info(`Invalid Api Key. ${queueNotProcessed}`);\n return;\n }\n\n this._processingQueue = true;\n\n try {\n var events = config.storage.getList('ex-q', config.submissionBatchSize);\n if (!events || events.length == 0) {\n this._processingQueue = false;\n return;\n }\n\n log.info(`Sending ${events.length} events to ${config.serverUrl}.`);\n config.submissionClient.postEvents(getEvents(events), config, (response:SubmissionResponse) => {\n this.processSubmissionResponse(response, events);\n log.info('Finished processing queue.');\n this._processingQueue = false;\n }, isAppExiting);\n } catch (ex) {\n log.error(`Error processing queue: ${ex}`);\n this.suspendProcessing();\n this._processingQueue = false;\n }\n }\n\n private processSubmissionResponse(response:SubmissionResponse, events:{ path:string, value:IEvent }[]): void {\n const noSubmission:string = 'The event will not be submitted.'; // Optimization for minifier.\n var config:Configuration = this._config; // Optimization for minifier.\n var log:ILog = config.log; // Optimization for minifier.\n\n if (response.success) {\n log.info(`Sent ${events.length} events.`);\n this.removeEvents(events);\n return;\n }\n\n if (response.serviceUnavailable) {\n // You are currently over your rate limit or the servers are under stress.\n log.error('Server returned service unavailable.');\n this.suspendProcessing();\n return;\n }\n\n if (response.paymentRequired) {\n // If the organization over the rate limit then discard the event.\n log.info('Too many events have been submitted, please upgrade your plan.');\n this.suspendProcessing(null, true, true);\n return;\n }\n\n if (response.unableToAuthenticate) {\n // The api key was suspended or could not be authorized.\n log.info(`Unable to authenticate, please check your configuration. ${noSubmission}`);\n this.suspendProcessing(15);\n this.removeEvents(events);\n return;\n }\n\n if (response.notFound || response.badRequest) {\n // The service end point could not be found.\n log.error(`Error while trying to submit data: ${response.message}`);\n this.suspendProcessing(60 * 4);\n this.removeEvents(events);\n return;\n }\n\n if (response.requestEntityTooLarge) {\n var message = 'Event submission discarded for being too large.';\n if (config.submissionBatchSize > 1) {\n log.error(`${message} Retrying with smaller batch size.`);\n config.submissionBatchSize = Math.max(1, Math.round(config.submissionBatchSize / 1.5));\n } else {\n log.error(`${message} ${noSubmission}`);\n this.removeEvents(events);\n }\n\n return;\n }\n\n if (!response.success) {\n log.error(`Error submitting events: ${response.message || 'Please check the network tab for more info.'}`);\n this.suspendProcessing();\n }\n }\n\n private ensureQueueTimer(): void {\n if (!this._queueTimer) {\n this._queueTimer = setInterval(() => this.onProcessQueue(), 10000);\n }\n }\n\n private onProcessQueue(): void {\n if (!this.isQueueProcessingSuspended() && !this._processingQueue) {\n this.process();\n }\n }\n\n public suspendProcessing(durationInMinutes?:number, discardFutureQueuedItems?:boolean, clearQueue?:boolean): void {\n var config:Configuration = this._config; // Optimization for minifier.\n\n if (!durationInMinutes || durationInMinutes <= 0) {\n durationInMinutes = 5;\n }\n\n config.log.info(`Suspending processing for ${durationInMinutes} minutes.`);\n this._suspendProcessingUntil = new Date(new Date().getTime() + (durationInMinutes * 60000));\n\n if (discardFutureQueuedItems) {\n this._discardQueuedItemsUntil = new Date(new Date().getTime() + (durationInMinutes * 60000));\n }\n\n if (clearQueue) {\n // Account is over the limit and we want to ensure that the sample size being sent in will contain newer errors.\n this.removeEvents(config.storage.getList('ex-q'));\n }\n }\n\n private removeEvents(events:{ path:string, value:IEvent }[]) {\n for (var index = 0; index < (events || []).length; index++) {\n this._config.storage.remove(events[index].path);\n }\n }\n\n private isQueueProcessingSuspended(): boolean {\n return this._suspendProcessingUntil && this._suspendProcessingUntil > new Date();\n }\n\n private areQueuedItemsDiscarded(): boolean {\n return this._discardQueuedItemsUntil && this._discardQueuedItemsUntil > new Date();\n }\n}\n\n \n\nexport class InMemoryStorage implements IStorage {\n private _items:IStorageItem[] = [];\n private _maxItems:number;\n\n constructor(maxItems?:number) {\n this._maxItems = maxItems > 0 ? maxItems : 250;\n }\n\n public save(path:string, value:T):boolean {\n if (!path || !value) {\n return false;\n }\n\n this.remove(path);\n if (this._items.push({ created: new Date().getTime(), path: path, value: value }) > this._maxItems) {\n this._items.shift();\n }\n\n return true;\n }\n\n public get(path:string):T {\n var item:IStorageItem = path ? this.getList(`^${path}$`, 1)[0] : null;\n return item ? item.value : null;\n }\n\n public getList(searchPattern?:string, limit?:number):IStorageItem[] {\n var items = this._items; // Optimization for minifier\n if (!searchPattern) {\n return items.slice(0, limit);\n }\n\n var regex = new RegExp(searchPattern);\n var results:IStorageItem[] = [];\n for (var index = 0; index < items.length; index++) {\n if (regex.test(items[index].path)) {\n results.push(items[index]);\n\n if (results.length >= limit) {\n break;\n }\n }\n }\n\n return results;\n }\n\n public remove(path:string):void {\n if (path) {\n var item = this.getList(`^${path}$`, 1)[0];\n if (item) {\n this._items.splice(this._items.indexOf(item), 1);\n }\n }\n }\n}\n\n \n\ndeclare var XDomainRequest:{ new (); create(); };\n\nexport class DefaultSubmissionClient implements ISubmissionClient {\n public configurationVersionHeader:string = 'x-exceptionless-configversion';\n\n public postEvents(events:IEvent[], config:Configuration, callback:(response:SubmissionResponse) => void, isAppExiting?:boolean):void {\n var data = Utils.stringify(events, config.dataExclusions);\n var request = this.createRequest(config, 'POST', '/api/v2/events', data);\n var cb = this.createSubmissionCallback(config, callback);\n\n return config.submissionAdapter.sendRequest(request, cb, isAppExiting);\n }\n\n public postUserDescription(referenceId:string, description:IUserDescription, config:Configuration, callback:(response:SubmissionResponse) => void):void {\n var path = `/api/v2/events/by-ref/${encodeURIComponent(referenceId)}/user-description`;\n var data = Utils.stringify(description, config.dataExclusions);\n var request = this.createRequest(config, 'POST', path, data);\n var cb = this.createSubmissionCallback(config, callback);\n\n return config.submissionAdapter.sendRequest(request, cb);\n }\n\n public getSettings(config:Configuration, callback:(response:SettingsResponse) => void):void {\n var request = this.createRequest(config, 'GET', '/api/v2/projects/config');\n var cb = (status, message, data?, headers?) => {\n if (status !== 200) {\n return callback(new SettingsResponse(false, null, -1, null, message));\n }\n\n var settings:IClientConfiguration;\n try {\n settings = JSON.parse(data);\n } catch (e) {\n config.log.error(`Unable to parse settings: '${data}'`);\n }\n\n if (!settings || isNaN(settings.version)) {\n return callback(new SettingsResponse(false, null, -1, null, 'Invalid configuration settings.'));\n }\n\n callback(new SettingsResponse(true, settings.settings || {}, settings.version));\n }\n\n return config.submissionAdapter.sendRequest(request, cb);\n }\n\n private createRequest(config: Configuration, method: string, path: string, data: string = null): SubmissionRequest {\n return {\n method,\n path,\n data,\n serverUrl: config.serverUrl,\n apiKey: config.apiKey,\n userAgent: config.userAgent\n };\n }\n\n private createSubmissionCallback(config:Configuration, callback:(response:SubmissionResponse) => void) {\n return (status, message, data?, headers?) => {\n var settingsVersion: number = headers && parseInt(headers[this.configurationVersionHeader]);\n SettingsManager.checkVersion(settingsVersion, config);\n\n callback(new SubmissionResponse(status, message));\n };\n }\n}\n\n \n\nexport class Utils {\n public static addRange(target:T[], ...values:T[]) {\n if (!target) {\n target = [];\n }\n\n if (!values || values.length === 0) {\n return target;\n }\n\n for (var index = 0; index < values.length; index++) {\n if (values[index] && target.indexOf(values[index]) < 0) {\n target.push(values[index]);\n }\n }\n\n return target;\n }\n\n public static getHashCode(source:string): string {\n if (!source || source.length === 0) {\n return null;\n }\n\n var hash:number = 0;\n for (var index = 0; index < source.length; index++) {\n var character = source.charCodeAt(index);\n hash = ((hash << 5) - hash) + character;\n hash |= 0;\n }\n\n return hash.toString();\n }\n\n public static getCookies(cookies:string): Object {\n var result:Object = {};\n\n var parts:string[] = (cookies || '').split('; ');\n for (var index = 0; index < parts.length; index++) {\n var cookie:string[] = parts[index].split('=');\n result[cookie[0]] = cookie[1];\n }\n\n return result;\n }\n\n public static guid(): string {\n function s4() {\n return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);\n }\n\n return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();\n }\n\n public static merge(defaultValues:Object, values:Object) {\n var result:Object = {};\n\n for (var key in defaultValues || {}) {\n if (!!defaultValues[key]) {\n result[key] = defaultValues[key];\n }\n }\n\n for (var key in values || {}) {\n if (!!values[key]) {\n result[key] = values[key];\n }\n }\n\n return result;\n }\n\n public static parseVersion(source:string): string {\n if (!source) {\n return null;\n }\n\n var versionRegex = /(v?((\\d+)\\.(\\d+)(\\.(\\d+))?)(?:-([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?(?:\\+([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?)/;\n var matches = versionRegex.exec(source);\n if (matches && matches.length > 0) {\n return matches[0];\n }\n\n return null;\n }\n\n public static parseQueryString(query:string) {\n if (!query || query.length === 0) {\n return null;\n }\n\n var pairs:string[] = query.split('&');\n if (pairs.length === 0) {\n return null;\n }\n\n var result:Object = {};\n for (var index = 0; index < pairs.length; index++) {\n var pair = pairs[index].split('=');\n result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);\n }\n\n return result;\n }\n\n public static randomNumber(): number {\n return Math.floor(Math.random() * 9007199254740992);\n }\n\n public static stringify(data:any, exclusions?:string[]): string {\n function checkForMatch(pattern:string, value:string): boolean {\n if (!pattern || !value || typeof value !== 'string') {\n return false;\n }\n\n var trim = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;\n pattern = pattern.toLowerCase().replace(trim, '');\n value = value.toLowerCase().replace(trim, '');\n\n if (pattern.length <= 0) {\n return false;\n }\n\n var startsWithWildcard:boolean = pattern[0] === '*';\n if (startsWithWildcard) {\n pattern = pattern.slice(1);\n }\n\n var endsWithWildcard:boolean = pattern[pattern.length - 1] === '*';\n if (endsWithWildcard) {\n pattern = pattern.substring(0, pattern.length - 1);\n }\n\n if (startsWithWildcard && endsWithWildcard)\n return value.indexOf(pattern) !== -1;\n\n if (startsWithWildcard)\n return value.lastIndexOf(pattern) === (value.length - pattern.length);\n\n if (endsWithWildcard)\n return value.indexOf(pattern) === 0;\n\n return value === pattern;\n }\n\n function stringifyImpl(data:any, exclusions:string[]): string {\n var cache:string[] = [];\n return JSON.stringify(data, function(key:string, value:any) {\n for (var index = 0; index < (exclusions || []).length; index++) {\n if (checkForMatch(exclusions[index], key)){\n return;\n }\n }\n\n if (typeof value === 'object' && !!value) {\n if (cache.indexOf(value) !== -1) {\n // Circular reference found, discard key\n return;\n }\n\n cache.push(value);\n }\n\n return value;\n });\n }\n\n if (({}).toString.call(data) === '[object Array]') {\n var result = [];\n for (var index = 0; index < data.length; index++) {\n result[index] = JSON.parse(stringifyImpl(data[index], exclusions || []));\n }\n\n return JSON.stringify(result);\n }\n\n return stringifyImpl(data, exclusions || []);\n }\n}\n\n \n\nexport class Configuration implements IConfigurationSettings {\n /**\n * A default list of tags that will automatically be added to every\n * report submitted to the server.\n *\n * @type {Array}\n */\n public defaultTags:string[] = [];\n\n /**\n * A default list of of extended data objects that will automatically\n * be added to every report submitted to the server.\n *\n * @type {{}}\n */\n public defaultData:Object = {};\n\n /**\n * Whether the client is currently enabled or not. If it is disabled,\n * submitted errors will be discarded and no data will be sent to the server.\n *\n * @returns {boolean}\n */\n public enabled:boolean = true;\n\n public environmentInfoCollector:IEnvironmentInfoCollector;\n public errorParser:IErrorParser;\n public lastReferenceIdManager:ILastReferenceIdManager = new DefaultLastReferenceIdManager();\n public log:ILog;\n public moduleCollector:IModuleCollector;\n public requestInfoCollector:IRequestInfoCollector;\n\n /**\n * Maximum number of events that should be sent to the server together in a batch. (Defaults to 50)\n */\n public submissionBatchSize:number;\n public submissionAdapter:ISubmissionAdapter;\n public submissionClient:ISubmissionClient;\n\n /**\n * Contains a dictionary of custom settings that can be used to control\n * the client and will be automatically updated from the server.\n */\n public settings:Object = {};\n\n public storage:IStorage;\n\n public queue:IEventQueue;\n\n constructor(configSettings?:IConfigurationSettings) {\n function inject(fn:any) {\n return typeof fn === 'function' ? fn(this) : fn;\n }\n\n configSettings = Utils.merge(Configuration.defaults, configSettings);\n\n this.log = inject(configSettings.log) || new NullLog();\n this.apiKey = configSettings.apiKey;\n this.serverUrl = configSettings.serverUrl;\n\n this.environmentInfoCollector = inject(configSettings.environmentInfoCollector);\n this.errorParser = inject(configSettings.errorParser);\n this.lastReferenceIdManager = inject(configSettings.lastReferenceIdManager) || new DefaultLastReferenceIdManager();\n this.moduleCollector = inject(configSettings.moduleCollector);\n this.requestInfoCollector = inject(configSettings.requestInfoCollector);\n this.submissionBatchSize = inject(configSettings.submissionBatchSize) || 50;\n this.submissionAdapter = inject(configSettings.submissionAdapter);\n this.submissionClient = inject(configSettings.submissionClient) || new DefaultSubmissionClient();\n this.storage = inject(configSettings.storage) || new InMemoryStorage();\n this.queue = inject(configSettings.queue) || new DefaultEventQueue(this);\n\n SettingsManager.applySavedServerSettings(this);\n EventPluginManager.addDefaultPlugins(this);\n }\n\n /**\n * The API key that will be used when sending events to the server.\n * @type {string}\n * @private\n */\n private _apiKey:string;\n\n /**\n * The API key that will be used when sending events to the server.\n * @returns {string}\n */\n public get apiKey():string {\n return this._apiKey;\n }\n\n /**\n * The API key that will be used when sending events to the server.\n * @param value\n */\n public set apiKey(value:string) {\n this._apiKey = value || null;\n this.log.info(`apiKey: ${this._apiKey}`);\n }\n\n /**\n * Returns true if the apiKey is valid.\n * @returns {boolean}\n */\n public get isValid():boolean {\n return !!this.apiKey && this.apiKey.length >= 10;\n }\n\n /**\n * The server url that all events will be sent to.\n * @type {string}\n * @private\n */\n private _serverUrl:string = 'https://collector.exceptionless.io';\n\n /**\n * The server url that all events will be sent to.\n * @returns {string}\n */\n public get serverUrl():string {\n return this._serverUrl;\n }\n\n /**\n * The server url that all events will be sent to.\n * @param value\n */\n public set serverUrl(value:string) {\n if (!!value) {\n this._serverUrl = value;\n this.log.info(`serverUrl: ${this._serverUrl}`);\n }\n }\n\n /**\n * A list of exclusion patterns.\n * @type {Array}\n * @private\n */\n private _dataExclusions:string[] = [];\n\n /**\n * A list of exclusion patterns that will automatically remove any data that\n * matches them from any data submitted to the server.\n *\n * For example, entering CreditCard will remove any extended data properties,\n * form fields, cookies and query parameters from the report.\n *\n * @returns {string[]}\n */\n public get dataExclusions():string[] {\n var exclusions:string = this.settings['@@DataExclusions'];\n return this._dataExclusions.concat(exclusions && exclusions.split(',') || []);\n }\n\n /**\n * Add items to the list of exclusion patterns that will automatically remove any\n * data that matches them from any data submitted to the server.\n *\n * For example, entering CreditCard will remove any extended data properties, form\n * fields, cookies and query parameters from the report.\n *\n * @param exclusions\n */\n public addDataExclusions(...exclusions:string[]) {\n this._dataExclusions = Utils.addRange(this._dataExclusions, ...exclusions);\n }\n\n /**\n * The list of plugins that will be used in this configuration.\n * @type {Array}\n * @private\n */\n private _plugins:IEventPlugin[] = [];\n\n /**\n * The list of plugins that will be used in this configuration.\n * @returns {IEventPlugin[]}\n */\n public get plugins():IEventPlugin[] {\n return this._plugins.sort((p1:IEventPlugin, p2:IEventPlugin) => {\n return (p1.priority < p2.priority) ? -1 : (p1.priority > p2.priority) ? 1 : 0;\n });\n }\n\n /**\n * Register an plugin to be used in this configuration.\n * @param plugin\n */\n public addPlugin(plugin:IEventPlugin): void;\n\n /**\n * Register an plugin to be used in this configuration.\n * @param name The name used to identify the plugin.\n * @param priority Used to determine plugins priority.\n * @param pluginAction A function that is run.\n */\n public addPlugin(name:string, priority:number, pluginAction:(context:EventPluginContext, next?:() => void) => void): void;\n public addPlugin(pluginOrName:IEventPlugin|string, priority?:number, pluginAction?:(context:EventPluginContext, next?:() => void) => void): void {\n var plugin:IEventPlugin = !!pluginAction ? { name: pluginOrName, priority: priority, run: pluginAction } : pluginOrName;\n if (!plugin || !plugin.run) {\n this.log.error('Add plugin failed: Run method not defined');\n return;\n }\n\n if (!plugin.name) {\n plugin.name = Utils.guid();\n }\n\n if (!plugin.priority) {\n plugin.priority = 0;\n }\n\n var pluginExists:boolean = false;\n var plugins = this._plugins; // optimization for minifier.\n for (var index = 0; index < plugins.length; index++) {\n if (plugins[index].name === plugin.name) {\n pluginExists = true;\n break;\n }\n }\n\n if (!pluginExists) {\n plugins.push(plugin);\n }\n }\n\n /**\n * Remove the plugin from this configuration.\n * @param plugin\n */\n public removePlugin(plugin:IEventPlugin): void;\n\n /**\n * Remove an plugin by key from this configuration.\n * @param name\n */\n public removePlugin(name:string): void;\n public removePlugin(pluginOrName:IEventPlugin|string): void {\n var name:string = typeof pluginOrName === 'string' ? pluginOrName : pluginOrName.name;\n if (!name) {\n this.log.error('Remove plugin failed: Plugin name not defined');\n return;\n }\n\n var plugins = this._plugins; // optimization for minifier.\n for (var index = 0; index < plugins.length; index++) {\n if (plugins[index].name === name) {\n plugins.splice(index, 1);\n break;\n }\n }\n }\n\n /**\n * Automatically set the application version for events.\n * @param version\n */\n public setVersion(version:string): void {\n if (!!version) {\n this.defaultData['@version'] = version;\n }\n }\n\n public setUserIdentity(userInfo:IUserInfo): void;\n public setUserIdentity(identity:string): void;\n public setUserIdentity(identity:string, name:string): void;\n public setUserIdentity(userInfoOrIdentity:IUserInfo|string, name?:string): void {\n const USER_KEY:string = '@user'; // optimization for minifier.\n var userInfo:IUserInfo = typeof userInfoOrIdentity !== 'string' ? userInfoOrIdentity : { identity: userInfoOrIdentity, name: name };\n\n var shouldRemove:boolean = !userInfo || (!userInfo.identity && !userInfo.name);\n if (shouldRemove) {\n delete this.defaultData[USER_KEY];\n } else {\n this.defaultData[USER_KEY] = userInfo;\n }\n\n this.log.info(`user identity: ${shouldRemove ? 'null' : userInfo.identity}`);\n }\n\n /**\n * Used to identify the client that sent the events to the server.\n * @returns {string}\n */\n public get userAgent():string {\n return 'exceptionless-js/1.0.0.0';\n }\n\n /**\n * Automatically set a reference id for error events.\n */\n public useReferenceIds(): void {\n this.addPlugin(new ReferenceIdPlugin());\n }\n\n // TODO: Support a min log level.\n public useDebugLogger(): void {\n this.log = new ConsoleLog();\n }\n\n /**\n * The default configuration settings that are applied to new configuration instances.\n * @type {IConfigurationSettings}\n * @private\n */\n private static _defaultSettings:IConfigurationSettings = null;\n\n /**\n * The default configuration settings that are applied to new configuration instances.\n * @returns {IConfigurationSettings}\n */\n public static get defaults() {\n if (Configuration._defaultSettings === null) {\n Configuration._defaultSettings = {};\n }\n\n return Configuration._defaultSettings;\n }\n}\n\n \n\nexport class EventBuilder {\n private _validIdentifierErrorMessage:string = \"must contain between 8 and 100 alphanumeric or '-' characters.\"; // optimization for minifier.\n\n public target:IEvent;\n public client:ExceptionlessClient;\n public pluginContextData:ContextData;\n\n constructor(event:IEvent, client:ExceptionlessClient, pluginContextData?:ContextData) {\n this.target = event;\n this.client = client;\n this.pluginContextData = pluginContextData || new ContextData();\n }\n\n public setType(type:string): EventBuilder {\n if (!!type) {\n this.target.type = type;\n }\n\n return this;\n }\n\n public setSource(source:string): EventBuilder {\n if (!!source) {\n this.target.source = source;\n }\n\n return this;\n }\n\n public setSessionId(sessionId:string): EventBuilder {\n if (!this.isValidIdentifier(sessionId)) {\n throw new Error(`SessionId ${this._validIdentifierErrorMessage}`);\n }\n\n this.target.session_id = sessionId;\n return this;\n }\n\n public setReferenceId(referenceId:string): EventBuilder {\n if (!this.isValidIdentifier(referenceId)) {\n throw new Error(`ReferenceId ${this._validIdentifierErrorMessage}`);\n }\n\n this.target.reference_id = referenceId;\n return this;\n }\n\n public setMessage(message:string): EventBuilder {\n if (!!message) {\n this.target.message = message;\n }\n\n return this;\n }\n\n public setGeo(latitude: number, longitude: number): EventBuilder {\n if (latitude < -90.0 || latitude > 90.0)\n throw new Error('Must be a valid latitude value between -90.0 and 90.0.');\n if (longitude < -180.0 || longitude > 180.0)\n throw new Error('Must be a valid longitude value between -180.0 and 180.0.');\n\n this.target.geo = `${latitude},${longitude}`;\n return this;\n }\n\n public setUserIdentity(userInfo:IUserInfo): EventBuilder;\n public setUserIdentity(identity:string): EventBuilder;\n public setUserIdentity(identity:string, name:string): EventBuilder;\n public setUserIdentity(userInfoOrIdentity:IUserInfo|string, name?:string): EventBuilder {\n var userInfo = typeof userInfoOrIdentity !== 'string' ? userInfoOrIdentity : { identity: userInfoOrIdentity, name: name };\n if (!userInfo || (!userInfo.identity && !userInfo.name)) {\n return this;\n }\n\n this.setProperty('@user', userInfo);\n return this;\n }\n\n public setValue(value:number): EventBuilder {\n if (!!value) {\n this.target.value = value;\n }\n\n return this;\n }\n\n public addTags(...tags:string[]): EventBuilder {\n this.target.tags = Utils.addRange(this.target.tags, ...tags);\n return this;\n }\n\n public setProperty(name:string, value:any): EventBuilder {\n if (!name || (value === undefined || value == null)) {\n return this;\n }\n\n if (!this.target.data) {\n this.target.data = {};\n }\n\n this.target.data[name] = value;\n return this;\n }\n\n public markAsCritical(critical:boolean): EventBuilder {\n if (critical) {\n this.addTags('Critical');\n }\n\n return this;\n }\n\n public addRequestInfo(request:Object): EventBuilder {\n if (!!request) {\n this.pluginContextData['@request'] = request;\n }\n\n return this;\n }\n\n public submit(callback?:(context:EventPluginContext) => void): void {\n this.client.submitEvent(this.target, this.pluginContextData, callback);\n }\n\n private isValidIdentifier(value:string): boolean {\n if (!value) {\n return true;\n }\n\n if (value.length < 8 || value.length > 100) {\n return false;\n }\n\n for (var index = 0; index < value.length; index++) {\n var code = value.charCodeAt(index);\n var isDigit = (code >= 48) && (code <= 57);\n var isLetter = ((code >= 65) && (code <= 90)) || ((code >= 97) && (code <= 122));\n var isMinus = code === 45;\n\n if (!(isDigit || isLetter) && !isMinus) {\n return false;\n }\n }\n\n return true;\n }\n}\n\n \n\nexport interface IError extends IInnerError {\n modules?:IModule[]\n}\n\nexport interface IUserDescription {\n email_address?:string;\n description?:string;\n data?:any;\n}\n\nexport class ContextData {\n public setException(exception:Error): void {\n if (exception) {\n this['@@_Exception'] = exception;\n }\n }\n\n public get hasException(): boolean {\n return !!this['@@_Exception']\n }\n\n public getException(): Error {\n return this['@@_Exception'] || null;\n }\n\n public markAsUnhandledError(): void {\n this['@@_IsUnhandledError'] = true;\n }\n\n public get isUnhandledError(): boolean {\n return !!this['@@_IsUnhandledError'];\n }\n\n public setSubmissionMethod(method:string): void {\n if (method) {\n this['@@_SubmissionMethod'] = method;\n }\n }\n\n public getSubmissionMethod(): string {\n return this['@@_SubmissionMethod'] || null;\n }\n}\n\nexport class SubmissionResponse {\n success:boolean = false;\n badRequest:boolean = false;\n serviceUnavailable:boolean = false;\n paymentRequired:boolean = false;\n unableToAuthenticate:boolean = false;\n notFound:boolean = false;\n requestEntityTooLarge:boolean = false;\n statusCode:number;\n message:string;\n\n constructor(statusCode:number, message?:string) {\n this.statusCode = statusCode;\n this.message = message;\n\n this.success = statusCode >= 200 && statusCode <= 299;\n this.badRequest = statusCode === 400;\n this.serviceUnavailable = statusCode === 503;\n this.paymentRequired = statusCode === 402;\n this.unableToAuthenticate = statusCode === 401 || statusCode === 403;\n this.notFound = statusCode === 404;\n this.requestEntityTooLarge = statusCode === 413;\n }\n}\n\n \n\nexport class ExceptionlessClient {\n public config:Configuration;\n\n constructor();\n constructor(settings:IConfigurationSettings);\n constructor(apiKey:string, serverUrl?:string);\n constructor(settingsOrApiKey?:IConfigurationSettings|string, serverUrl?:string) {\n if (typeof settingsOrApiKey !== 'object') {\n this.config = new Configuration(settingsOrApiKey);\n } else {\n this.config = new Configuration({ apiKey: settingsOrApiKey, serverUrl: serverUrl });\n }\n }\n\n public createException(exception:Error): EventBuilder {\n var pluginContextData = new ContextData();\n pluginContextData.setException(exception);\n return this.createEvent(pluginContextData).setType('error');\n }\n\n public submitException(exception:Error, callback?:(context:EventPluginContext) => void): void {\n this.createException(exception).submit(callback);\n }\n\n public createUnhandledException(exception:Error, submissionMethod?:string): EventBuilder {\n var builder = this.createException(exception);\n builder.pluginContextData.markAsUnhandledError();\n builder.pluginContextData.setSubmissionMethod(submissionMethod);\n\n return builder;\n }\n\n public submitUnhandledException(exception:Error, submissionMethod?:string, callback?:(context:EventPluginContext) => void) {\n this.createUnhandledException(exception, submissionMethod).submit(callback);\n }\n\n public createFeatureUsage(feature:string): EventBuilder {\n return this.createEvent().setType('usage').setSource(feature);\n }\n\n public submitFeatureUsage(feature:string, callback?:(context:EventPluginContext) => void): void {\n this.createFeatureUsage(feature).submit(callback);\n }\n\n public createLog(message:string): EventBuilder;\n public createLog(source:string, message:string): EventBuilder;\n public createLog(source:string, message:string, level:string): EventBuilder;\n public createLog(sourceOrMessage:string, message?:string, level?:string): EventBuilder {\n var builder = this.createEvent().setType('log');\n\n if (message && level) {\n builder = builder.setSource(sourceOrMessage).setMessage(message).setProperty('@level', level);\n } else if (message) {\n builder = builder.setSource(sourceOrMessage).setMessage(message);\n } else {\n // TODO: Look into using https://www.stevefenton.co.uk/Content/Blog/Date/201304/Blog/Obtaining-A-Class-Name-At-Runtime-In-TypeScript/\n var caller:any = arguments.callee.caller;\n builder = builder.setSource(caller && caller.name).setMessage(sourceOrMessage);\n }\n\n return builder;\n }\n\n public submitLog(message:string): void;\n public submitLog(source:string, message:string): void;\n public submitLog(source:string, message:string, level:string, callback?:(context:EventPluginContext) => void): void;\n public submitLog(sourceOrMessage:string, message?:string, level?:string, callback?:(context:EventPluginContext) => void): void {\n this.createLog(sourceOrMessage, message, level).submit(callback);\n }\n\n public createNotFound(resource:string): EventBuilder {\n return this.createEvent().setType('404').setSource(resource);\n }\n\n public submitNotFound(resource:string, callback?:(context:EventPluginContext) => void): void {\n this.createNotFound(resource).submit(callback);\n }\n\n public createSessionStart(sessionId:string): EventBuilder {\n return this.createEvent().setType('start').setSessionId(sessionId);\n }\n\n public submitSessionStart(sessionId:string, callback?:(context:EventPluginContext) => void): void {\n this.createSessionStart(sessionId).submit(callback);\n }\n\n public createSessionEnd(sessionId:string): EventBuilder {\n return this.createEvent().setType('end').setSessionId(sessionId);\n }\n\n public submitSessionEnd(sessionId:string, callback?:(context:EventPluginContext) => void): void {\n this.createSessionEnd(sessionId).submit(callback);\n }\n\n public createEvent(pluginContextData?:ContextData): EventBuilder {\n return new EventBuilder({ date: new Date() }, this, pluginContextData);\n }\n\n /**\n * Submits the event to be sent to the server.\n * @param event The event data.\n * @param pluginContextData Any contextual data objects to be used by Exceptionless plugins to gather default information for inclusion in the report information.\n * @param callback\n */\n public submitEvent(event:IEvent, pluginContextData?:ContextData, callback?:(context:EventPluginContext) => void): void {\n function cancelled() {\n if (!!context) {\n context.cancelled = true;\n }\n\n return !!callback && callback(context);\n }\n\n if (!event) {\n return cancelled();\n }\n\n if (!this.config.enabled) {\n this.config.log.info('Event submission is currently disabled.');\n return cancelled();\n }\n\n if (!event.data) {\n event.data = {};\n }\n\n if (!event.tags || !event.tags.length) {\n event.tags = [];\n }\n\n var context = new EventPluginContext(this, event, pluginContextData);\n EventPluginManager.run(context, function (context:EventPluginContext) {\n let ev = context.event;\n if (!context.cancelled) {\n // ensure all required data\n if (!ev.type || ev.type.length === 0) {\n ev.type = 'log';\n }\n\n if (!ev.date) {\n ev.date = new Date();\n }\n\n var config = context.client.config;\n config.queue.enqueue(ev);\n\n if (ev.reference_id && ev.reference_id.length > 0) {\n context.log.info(`Setting last reference id '${ev.reference_id}'`);\n config.lastReferenceIdManager.setLast(ev.reference_id);\n }\n }\n\n !!callback && callback(context);\n });\n }\n\n /**\n * Updates the user's email address and description of an event for the specified reference id.\n * @param referenceId The reference id of the event to update.\n * @param email The user's email address to set on the event.\n * @param description The user's description of the event.\n */\n public updateUserEmailAndDescription(referenceId:string, email:string, description:string, callback?:(response:SubmissionResponse) => void) {\n if (!referenceId || !email || !description || !this.config.enabled) {\n return !!callback && callback(new SubmissionResponse(500, 'cancelled'));\n }\n\n var userDescription:IUserDescription = { email_address: email, description: description };\n var response = this.config.submissionClient.postUserDescription(referenceId, userDescription, this.config, (response:SubmissionResponse) => {\n if (!response.success) {\n this.config.log.error(`Failed to submit user email and description for event '${referenceId}': ${response.statusCode} ${response.message}`)\n }\n\n !!callback && callback(response);\n });\n }\n\n /**\n * Gets the last event client id that was submitted to the server.\n * @returns {string} The event client id.\n */\n public getLastReferenceId(): string {\n return this.config.lastReferenceIdManager.getLast();\n }\n\n /**\n * The default ExceptionlessClient instance.\n * @type {ExceptionlessClient}\n * @private\n */\n private static _instance:ExceptionlessClient = null;\n\n\n /**\n * The default ExceptionlessClient instance.\n * @type {ExceptionlessClient}\n */\n public static get default() {\n if(ExceptionlessClient._instance === null) {\n ExceptionlessClient._instance = new ExceptionlessClient(null);\n }\n\n return ExceptionlessClient._instance;\n }\n}\n\nexport interface IParameter {\n data?:any;\n generic_arguments?:string[];\n\n name?:string;\n type?:string;\n type_namespace?:string;\n}\n\n \n\nexport interface IMethod {\n data?:any;\n generic_arguments?:string[];\n parameters?:IParameter[];\n\n is_signature_target?:boolean;\n declaring_namespace?:string;\n declaring_type?:string;\n name?:string;\n module_id?:number;\n}\n\n \n\nexport interface IStackFrame extends IMethod {\n file_name?:string;\n line_number?:number;\n column?:number;\n}\n\n \n\nexport interface IInnerError {\n message?:string;\n type?:string;\n code?:string;\n data?:any;\n inner?:IInnerError\n stack_trace?:IStackFrame[];\n target_method?:IMethod;\n}\n\nexport interface IModule {\n data?:any;\n\n module_id?:number;\n name?:string;\n version?:string;\n is_entry?:boolean;\n created_date?:Date;\n modified_date?:Date;\n}\n\nexport interface IRequestInfo {\n user_agent?:string;\n http_method?:string;\n is_secure?:boolean;\n host?:string;\n port?:number;\n path?:string;\n referrer?:string;\n client_ip_address?:string;\n cookies?:any;\n post_data?:any;\n query_string?:any;\n data?:any\n}\n\nexport interface IEnvironmentInfo {\n processor_count?:number;\n total_physical_memory?:number;\n available_physical_memory?:number;\n command_line?:string;\n process_name?:string;\n process_id?:string;\n process_memory_size?:number;\n thread_id?:string;\n architecture?:string;\n o_s_name?:string;\n o_s_version?:string;\n ip_address?:string;\n machine_name?:string;\n install_id?:string;\n runtime_version?:string;\n data?:any;\n}\n\n \n\nexport class ConfigurationDefaultsPlugin implements IEventPlugin {\n public priority:number = 10;\n public name:string = 'ConfigurationDefaultsPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n var defaultTags:string[] = context.client.config.defaultTags || [];\n for (var index = 0; index < defaultTags.length; index++) {\n var tag = defaultTags[index];\n if (!!tag && context.event.tags.indexOf(tag) < 0) {\n context.event.tags.push(tag)\n }\n }\n\n var defaultData:Object = context.client.config.defaultData || {};\n for (var key in defaultData) {\n if (!!defaultData[key]) {\n context.event.data[key] = defaultData[key];\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class ErrorPlugin implements IEventPlugin {\n public priority:number = 30;\n public name:string = 'ErrorPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ERROR_KEY:string = '@error'; // optimization for minifier.\n\n var exception = context.contextData.getException();\n if (!!exception) {\n context.event.type = 'error';\n\n if (!context.event.data[ERROR_KEY]) {\n var parser = context.client.config.errorParser;\n if (!parser) {\n throw new Error('No error parser was defined.');\n }\n\n var result = parser.parse(context, exception);\n if (!!result) {\n context.event.data[ERROR_KEY] = result;\n }\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class ModuleInfoPlugin implements IEventPlugin {\n public priority:number = 40;\n public name:string = 'ModuleInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ERROR_KEY:string = '@error'; // optimization for minifier.\n\n var collector = context.client.config.moduleCollector;\n if (context.event.data[ERROR_KEY] && !context.event.data['@error'].modules && !!collector) {\n var modules = collector.getModules(context);\n if (modules && modules.length > 0) {\n context.event.data[ERROR_KEY].modules = modules;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class RequestInfoPlugin implements IEventPlugin {\n public priority:number = 60;\n public name:string = 'RequestInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const REQUEST_KEY:string = '@request'; // optimization for minifier.\n\n var collector = context.client.config.requestInfoCollector;\n if (!context.event.data[REQUEST_KEY] && !!collector) {\n var requestInfo = collector.getRequestInfo(context);\n if (!!requestInfo) {\n context.event.data[REQUEST_KEY] = requestInfo;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class EnvironmentInfoPlugin implements IEventPlugin {\n public priority:number = 70;\n public name:string = 'EnvironmentInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ENVIRONMENT_KEY:string = '@environment'; // optimization for minifier.\n\n var collector = context.client.config.environmentInfoCollector;\n if (!context.event.data[ENVIRONMENT_KEY] && collector) {\n var environmentInfo = collector.getEnvironmentInfo(context);\n if (!!environmentInfo) {\n context.event.data[ENVIRONMENT_KEY] = environmentInfo;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class SubmissionMethodPlugin implements IEventPlugin {\n public priority:number = 100;\n public name:string = 'SubmissionMethodPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n var submissionMethod:string = context.contextData.getSubmissionMethod();\n if (!!submissionMethod) {\n context.event.data['@submission_method'] = submissionMethod;\n }\n\n next && next();\n }\n}\n\nexport interface IStorageItem {\n created:number;\n path:string;\n value:T;\n}\n\nexport interface SubmissionCallback {\n (status: number, message: string, data?: string, headers?: Object): void\n}\n\nexport interface SubmissionRequest {\n serverUrl: string;\n apiKey: string;\n userAgent: string;\n method: string;\n path: string;\n data: string;\n}\n\nexport class SettingsResponse {\n success:boolean = false;\n settings:any;\n settingsVersion:number = -1;\n message:string;\n exception:any;\n\n constructor(success:boolean, settings:any, settingsVersion:number = -1, exception:any = null, message:string = null) {\n this.success = success;\n this.settings = settings;\n this.settingsVersion = settingsVersion;\n this.exception = exception;\n this.message = message;\n }\n}\n\nexport interface IClientConfiguration {\n settings:Object;\n version:number;\n}\n\nimport os = require('os');\nimport nodestacktrace = require('stack-trace');\nimport path = require('path');\nimport https = require('https');\nimport url = require('url');\n \n\nexport class NodeEnvironmentInfoCollector implements IEnvironmentInfoCollector {\n public getEnvironmentInfo(context:EventPluginContext): IEnvironmentInfo {\n function getIpAddresses():string {\n var ips:string[] = [];\n var interfaces = os.networkInterfaces();\n Object.keys(interfaces).forEach((name) => {\n interfaces[name].forEach((iface:any) => {\n if ('IPv4' === iface.family && !iface.internal) {\n ips.push(iface.address);\n }\n });\n });\n\n return ips.join(', ');\n }\n\n if (!os) {\n return null;\n }\n \n var environmentInfo: IEnvironmentInfo = {\n processor_count: os.cpus().length,\n total_physical_memory: os.totalmem(),\n available_physical_memory: os.freemem(),\n command_line: process.argv.join(' '),\n process_name: (process.title || '').replace(/[\\uE000-\\uF8FF]/g, ''),\n process_id: process.pid + '',\n process_memory_size: process.memoryUsage().heapTotal,\n //thread_id: '',\n architecture: os.arch(),\n o_s_name: os.type(),\n o_s_version: os.release(),\n ip_address: getIpAddresses(),\n machine_name: os.hostname(),\n //install_id: '',\n runtime_version: process.version,\n data: {\n loadavg: os.loadavg(),\n platform: os.platform(),\n tmpdir: os.tmpdir(),\n uptime: os.uptime()\n }\n };\n\n if ((os).endianness) {\n environmentInfo.data.endianness = (os).endianness();\n }\n\n return environmentInfo;\n }\n}\n\n \n\nexport class NodeErrorParser implements IErrorParser {\n public parse(context:EventPluginContext, exception:Error): IError {\n function getStackFrames(context:EventPluginContext, stackFrames:any[]): IStackFrame[] {\n var frames:IStackFrame[] = [];\n\n for (var index = 0; index < stackFrames.length; index++) {\n var frame = stackFrames[index];\n frames.push({\n name: frame.getMethodName() || frame.getFunctionName(),\n //parameters: frame.args,\n file_name: frame.getFileName(),\n line_number: frame.getLineNumber() || 0,\n column: frame.getColumnNumber() || 0,\n declaring_type: frame.getTypeName(),\n data: {\n is_native: frame.isNative() || (!!frame.filename && frame.filename[0] !== '/' && frame.filename[0] !== '.')\n }\n });\n }\n\n return frames;\n }\n\n if (!nodestacktrace) {\n throw new Error('Unable to load the stack trace library.');\n }\n\n var stackFrames = nodestacktrace.parse(exception) || [];\n return {\n type: exception.name,\n message: exception.message,\n stack_trace: getStackFrames(context, stackFrames)\n };\n }\n}\n\n \n\nexport class NodeModuleCollector implements IModuleCollector {\n\n private initialized:boolean = false;\n private installedModules:{[id:string]: IModule} = {};\n\n public getModules(context:EventPluginContext): IModule[] {\n\n this.initialize();\n\n if (!require.main) return [];\n\n var modulePath = path.dirname(require.main.filename) + '/node_modules/';\n var pathLength = modulePath.length;\n var loadedKeys = Object.keys(require.cache);\n var loadedModules = {};\n loadedKeys.forEach(key => {\n var id = key.substr(pathLength);\n console.log(id);\n id = id.substr(0, id.indexOf('/'));\n loadedModules[id] = true;\n });\n\n return Object.keys(loadedModules)\n .map(key => this.installedModules[key])\n .filter(m => m !== undefined);\n }\n\n private initialize() {\n if (this.initialized) return;\n this.initialized = true;\n\n var output = child.spawnSync('npm', ['ls', '--depth=0', '--json']).stdout;\n\n if (!output) return;\n\n var json;\n try {\n json = JSON.parse(output.toString());\n }\n catch (e) { return; }\n\n var items = json.dependencies;\n if (!items) return;\n\n var id = 0;\n this.installedModules = {};\n\n Object.keys(items).forEach(key => {\n var item = items[key];\n var theModule = {\n module_id: id++,\n name: key,\n version: item.version\n };\n\n this.installedModules[key] = theModule;\n });\n }\n}\n\n \n\nexport class NodeRequestInfoCollector implements IRequestInfoCollector {\n getRequestInfo(context:EventPluginContext):IRequestInfo {\n const REQUEST_KEY:string = '@request'; // optimization for minifier.\n if (!context.contextData[REQUEST_KEY]) {\n return null;\n }\n\n var request = context.contextData[REQUEST_KEY];\n var requestInfo:IRequestInfo = {\n client_ip_address: request.ip,\n user_agent: request.headers['user-agent'],\n is_secure: request.secure,\n http_method: request.method,\n host: request.hostname || request.host,\n path: request.path,\n post_data: request.body,\n //referrer: TODO,\n cookies: Utils.getCookies((request || {}).headers['cookie']),\n query_string: request.params\n };\n\n var host = request.headers['host'];\n var port:number = host && parseInt(host.slice(host.indexOf(':') + 1));\n if (port > 0) {\n requestInfo.port = port;\n }\n\n return requestInfo;\n }\n}\n\n \n\nexport class NodeSubmissionAdapter implements ISubmissionAdapter {\n public sendRequest(request:SubmissionRequest, callback:SubmissionCallback, isAppExiting?:boolean) {\n if (isAppExiting) {\n this.sendRequestSync(request, callback);\n return;\n }\n\n var parsedHost = url.parse(request.serverUrl);\n\n var options: https.RequestOptions = {\n auth: `client:${request.apiKey}`,\n headers: {},\n hostname: parsedHost.hostname,\n method: request.method,\n port: parsedHost.port && parseInt(parsedHost.port),\n path: request.path\n };\n\n options.headers['User-Agent'] = request.userAgent;\n\n if (request.method === 'POST') {\n options.headers = {\n 'Content-Type': 'application/json',\n 'Content-Length': request.data.length\n }\n }\n\n var protocol = (parsedHost.protocol === 'https' ? https : http);\n var clientRequest: http.ClientRequest = protocol.request(options, (response: http.IncomingMessage) => {\n var body = '';\n response.setEncoding('utf8');\n response.on('data', (chunk) => body += chunk);\n response.on('end', () => this.complete(response, body, response.headers, callback));\n });\n\n clientRequest.on('error', (error: Error) => callback(500, error.message));\n clientRequest.end(request.data);\n }\n\n private complete(response: http.IncomingMessage, responseBody: string, responseHeaders: Object, callback: SubmissionCallback): void {\n var message: string;\n if (response.statusCode === 0) {\n message = 'Unable to connect to server.';\n } else if (response.statusCode < 200 || response.statusCode > 299) {\n message = response.statusMessage || (response).message;\n }\n\n callback(response.statusCode || 500, message, responseBody, responseHeaders);\n }\n\n private sendRequestSync(request: SubmissionRequest, callback: SubmissionCallback): void {\n var requestJson = JSON.stringify(request);\n var res = child.spawnSync(process.execPath, [require.resolve('./submitSync.js')],\n {\n input: requestJson,\n stdio: ['pipe', 'pipe', process.stderr]\n });\n\n var out = res.stdout.toString();\n var result = JSON.parse(out);\n\n callback(result.status, result.message, result.data, result.headers);\n }\n}\n\n \n\nconst EXIT: string = 'exit';\nconst UNCAUGHT_EXCEPTION: string = 'uncaughtException';\nconst SIGINT: string = 'SIGINT';\nconst SIGINT_CODE: number = 2;\n\nvar defaults = Configuration.defaults;\ndefaults.environmentInfoCollector = new NodeEnvironmentInfoCollector();\ndefaults.errorParser = new NodeErrorParser();\ndefaults.moduleCollector = new NodeModuleCollector();\ndefaults.requestInfoCollector = new NodeRequestInfoCollector();\ndefaults.submissionAdapter = new NodeSubmissionAdapter();\n\nfunction getListenerCount(emitter, event:string): number {\n if (emitter.listenerCount) {\n return emitter.listenerCount(event);\n }\n return require(\"events\").listenerCount(emitter, event);\n}\n\n/*\n * Adding a event handler for 'uncaughtException' modifies the default\n * Node behavior, so it won't exit or log to the console. Instead,\n * we hijack the event emitter and forward the exception to the callback.\n */\nfunction onUncaughtException(callback: (error: Error) => void) {\n var originalEmit = process.emit;\n\n process.emit = function(type: string, error: Error) {\n if (type === UNCAUGHT_EXCEPTION) {\n callback(error);\n }\n\n return originalEmit.apply(this, arguments);\n }\n}\n\nonUncaughtException(function(error: Error) {\n ExceptionlessClient.default.submitUnhandledException(error, UNCAUGHT_EXCEPTION);\n});\n\n/*\n * We cannot hijack SIGINT, so if there are no other handlers,\n * we just reproduce default Node.js behavior by exiting.\n */\nprocess.on(SIGINT, function() {\n if (getListenerCount(process, SIGINT) <= 1) {\n process.exit(128 + SIGINT_CODE);\n }\n})\n\nprocess.on(EXIT, function(code: number) {\n /**\n * exit codes: https://nodejs.org/api/process.html#process_event_exit\n * From now on, only synchronous code may run. As soon as this method\n * ends, the application inevitably will exit.\n */\n function getExitCodeReason(code: number): string {\n if (code === 1) {\n return 'Uncaught Fatal Exception';\n }\n\n if (code === 3) {\n return 'Internal JavaScript Parse Error';\n }\n\n if (code === 4) {\n return 'Internal JavaScript Evaluation Failure';\n }\n\n if (code === 5) {\n return 'Fatal Exception';\n }\n\n if (code === 6) {\n return 'Non-function Internal Exception Handler ';\n }\n\n if (code === 7) {\n return 'Internal Exception Handler Run-Time Failure';\n }\n\n if (code === 8) {\n return 'Uncaught Exception';\n }\n\n if (code === 9) {\n return 'Invalid Argument';\n }\n\n if (code === 10) {\n return 'Internal JavaScript Run-Time Failure';\n }\n\n if (code === 12) {\n return 'Invalid Debug Argument';\n }\n\n return null;\n }\n\n var client = ExceptionlessClient.default;\n var config = client.config;\n var message = getExitCodeReason(code);\n\n if (message !== null) {\n client.submitLog(EXIT, message, 'Error')\n }\n\n config.queue.process(true);\n\n // Application will now exit.\n});\n\n(Error).stackTraceLimit = Infinity;\n\n"]} \ No newline at end of file diff --git a/src/exceptionless.node.ts b/src/exceptionless.node.ts index 349877cb..278b374f 100644 --- a/src/exceptionless.node.ts +++ b/src/exceptionless.node.ts @@ -37,6 +37,7 @@ import { IModuleCollector } from './services/IModuleCollector'; import { IRequestInfoCollector } from './services/IRequestInfoCollector'; import { NodeEnvironmentInfoCollector } from './services/NodeEnvironmentInfoCollector'; import { NodeErrorParser } from './services/NodeErrorParser'; +import { NodeModuleCollector } from './services/NodeModuleCollector'; import { NodeRequestInfoCollector } from './services/NodeRequestInfoCollector'; import { InMemoryStorage } from './storage/InMemoryStorage'; import { IStorage } from './storage/IStorage'; @@ -56,6 +57,7 @@ const SIGINT_CODE: number = 2; var defaults = Configuration.defaults; defaults.environmentInfoCollector = new NodeEnvironmentInfoCollector(); defaults.errorParser = new NodeErrorParser(); +defaults.moduleCollector = new NodeModuleCollector(); defaults.requestInfoCollector = new NodeRequestInfoCollector(); defaults.submissionAdapter = new NodeSubmissionAdapter(); diff --git a/src/services/NodeModuleCollector.ts b/src/services/NodeModuleCollector.ts new file mode 100644 index 00000000..45a40d12 --- /dev/null +++ b/src/services/NodeModuleCollector.ts @@ -0,0 +1,68 @@ +import { IModule } from '../models/IModule'; +import { IModuleCollector } from './IModuleCollector'; +import { EventPluginContext } from '../plugins/EventPluginContext'; +import { Utils } from '../Utils'; + +import child = require('child_process'); +import path = require('path'); + +export class NodeModuleCollector implements IModuleCollector { + + private initialized:boolean = false; + private installedModules:{[id:string]: IModule} = {}; + + public getModules(context:EventPluginContext): IModule[] { + this.initialize(); + + if (!require.main) return []; + + var modulePath = path.dirname(require.main.filename) + '/node_modules/'; + var pathLength = modulePath.length; + + var loadedKeys = Object.keys(require.cache); + var loadedModules = {}; + + loadedKeys.forEach(key => { + var id = key.substr(pathLength); + console.log(id); + id = id.substr(0, id.indexOf('/')); + loadedModules[id] = true; + }); + + return Object.keys(loadedModules) + .map(key => this.installedModules[key]) + .filter(m => m !== undefined); + } + + private initialize() { + if (this.initialized) return; + this.initialized = true; + + var output = child.spawnSync('npm', ['ls', '--depth=0', '--json']).stdout; + + if (!output) return; + + var json; + try { + json = JSON.parse(output.toString()); + } + catch (e) { return; } + + var items = json.dependencies; + if (!items) return; + + var id = 0; + this.installedModules = {}; + + Object.keys(items).forEach(key => { + var item = items[key]; + var theModule = { + module_id: id++, + name: key, + version: item.version + }; + + this.installedModules[key] = theModule; + }); + } +} From a23cce4e48a35662efd2c8b520d7e3881dcf014e Mon Sep 17 00:00:00 2001 From: Frank Ebersoll Date: Tue, 20 Oct 2015 23:01:53 +0200 Subject: [PATCH 3/7] Applied coding guidelines to NodeModuleCollector --- dist/exceptionless.node.js | 12 ++++++--- dist/exceptionless.node.js.map | 2 +- src/services/NodeModuleCollector.ts | 42 ++++++++++++++++++----------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/dist/exceptionless.node.js b/dist/exceptionless.node.js index dd27f9c8..87ecbf68 100644 --- a/dist/exceptionless.node.js +++ b/dist/exceptionless.node.js @@ -1297,8 +1297,9 @@ var NodeModuleCollector = (function () { NodeModuleCollector.prototype.getModules = function (context) { var _this = this; this.initialize(); - if (!require.main) + if (!require.main) { return []; + } var modulePath = path.dirname(require.main.filename) + '/node_modules/'; var pathLength = modulePath.length; var loadedKeys = Object.keys(require.cache); @@ -1315,12 +1316,14 @@ var NodeModuleCollector = (function () { }; NodeModuleCollector.prototype.initialize = function () { var _this = this; - if (this.initialized) + if (this.initialized) { return; + } this.initialized = true; var output = child.spawnSync('npm', ['ls', '--depth=0', '--json']).stdout; - if (!output) + if (!output) { return; + } var json; try { json = JSON.parse(output.toString()); @@ -1329,8 +1332,9 @@ var NodeModuleCollector = (function () { return; } var items = json.dependencies; - if (!items) + if (!items) { return; + } var id = 0; this.installedModules = {}; Object.keys(items).forEach(function (key) { diff --git a/dist/exceptionless.node.js.map b/dist/exceptionless.node.js.map index f6855f8e..dbc51741 100644 --- a/dist/exceptionless.node.js.map +++ b/dist/exceptionless.node.js.map @@ -1 +1 @@ -{"version":3,"file":"exceptionless.node.js","sourceRoot":"/source/","sources":["exceptionless.node.ts"],"names":["getListenerCount","onUncaughtException","getExitCodeReason"],"mappings":"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","sourcesContent":["import * as events from \"events\";\nimport * as net from \"net\";\nimport * as stream from \"stream\";\nimport * as child from \"child_process\";\nimport * as tls from \"tls\";\nimport * as http from \"http\";\nimport * as crypto from \"crypto\";\nexport interface IEvent {\n type?:string;\n source?:string;\n date?:Date;\n tags?:string[];\n message?:string;\n geo?:string;\n value?:number;\n data?:any;\n reference_id?:string;\n session_id?:string;\n}\n\nexport interface ILastReferenceIdManager {\n getLast(): string;\n clearLast(): void;\n setLast(eventId:string): void;\n}\n\nexport interface ILog {\n info(message:string):void;\n warn(message:string):void;\n error(message:string):void;\n}\n\n \n\nexport interface IEventQueue {\n enqueue(event:IEvent):void;\n process(isAppExiting?:boolean):void;\n suspendProcessing(durationInMinutes?:number, discardFutureQueuedItems?:boolean, clearQueue?:boolean):void;\n}\n\n \n\nexport interface IEnvironmentInfoCollector {\n getEnvironmentInfo(context:EventPluginContext):IEnvironmentInfo;\n}\n\n \n\nexport interface IErrorParser {\n parse(context:EventPluginContext, exception:Error): IError;\n}\n\n \n\nexport interface IModuleCollector {\n getModules(context:EventPluginContext):IModule[];\n}\n\n \n\nexport interface IRequestInfoCollector {\n getRequestInfo(context:EventPluginContext):IRequestInfo;\n}\n\n \n\nexport interface IStorage {\n save(path:string, value:T):boolean;\n get(path:string):T;\n getList(searchPattern?:string, limit?:number):IStorageItem[];\n remove(path:string):void;\n}\n\n \n\nexport interface ISubmissionAdapter {\n sendRequest(request:SubmissionRequest, callback:SubmissionCallback, isAppExiting?:boolean): void;\n}\n\n \n\nexport interface ISubmissionClient {\n postEvents(events:IEvent[], config:Configuration, callback:(response:SubmissionResponse) => void, isAppExiting?:boolean):void;\n postUserDescription(referenceId:string, description:IUserDescription, config:Configuration, callback:(response:SubmissionResponse) => void):void;\n getSettings(config:Configuration, callback:(response:SettingsResponse) => void):void;\n}\n\n \n\nexport interface IConfigurationSettings {\n apiKey?:string;\n serverUrl?:string;\n environmentInfoCollector?:IEnvironmentInfoCollector;\n errorParser?:IErrorParser;\n lastReferenceIdManager?:ILastReferenceIdManager;\n log?:ILog;\n moduleCollector?:IModuleCollector;\n requestInfoCollector?:IRequestInfoCollector;\n submissionBatchSize?:number;\n submissionClient?:ISubmissionClient;\n submissionAdapter?:ISubmissionAdapter;\n storage?:IStorage;\n queue?:IEventQueue;\n}\n\n \n\nexport class SettingsManager {\n /**\n * The configuration settings path.\n * @type {string}\n * @private\n */\n private static _configPath:string = 'ex-server-settings.json';\n\n /**\n * A list of handlers that will be fired when the settings change.\n * @type {Array}\n * @private\n */\n private static _handlers:{ (config:Configuration):void }[] = [];\n\n public static onChanged(handler:(config:Configuration) => void) {\n !!handler && this._handlers.push(handler);\n }\n\n public static applySavedServerSettings(config:Configuration):void {\n config.log.info('Applying saved settings.');\n config.settings = Utils.merge(config.settings, this.getSavedServerSettings(config));\n this.changed(config);\n }\n\n public static checkVersion(version:number, config:Configuration):void {\n if (version) {\n let savedConfigVersion = parseInt(config.storage.get(`${this._configPath}-version`), 10);\n if (isNaN(savedConfigVersion) || version > savedConfigVersion) {\n config.log.info(`Updating settings from v${(!isNaN(savedConfigVersion) ? savedConfigVersion : 0)} to v${version}`);\n this.updateSettings(config);\n }\n }\n }\n\n public static updateSettings(config:Configuration):void {\n if (!config.isValid) {\n config.log.error('Unable to update settings: ApiKey is not set.');\n return;\n }\n\n config.submissionClient.getSettings(config, (response:SettingsResponse) => {\n if (!response || !response.success || !response.settings) {\n return;\n }\n\n config.settings = Utils.merge(config.settings, response.settings);\n\n // TODO: Store snapshot of settings after reading from config and attributes and use that to revert to defaults.\n // Remove any existing server settings that are not in the new server settings.\n let savedServerSettings = SettingsManager.getSavedServerSettings(config);\n for (let key in savedServerSettings) {\n if (response.settings[key]) {\n continue;\n }\n\n delete config.settings[key];\n }\n\n let path = SettingsManager._configPath; // optimization for minifier.\n config.storage.save(`${path}-version`, response.settingsVersion);\n config.storage.save(path, response.settings);\n\n config.log.info('Updated settings');\n this.changed(config);\n });\n }\n\n private static changed(config:Configuration) {\n let handlers = this._handlers; // optimization for minifier.\n for (let index = 0; index < handlers.length; index++) {\n handlers[index](config);\n }\n }\n\n private static getSavedServerSettings(config:Configuration):Object {\n return config.storage.get(this._configPath) || {};\n }\n}\n\n \n\nexport class DefaultLastReferenceIdManager implements ILastReferenceIdManager {\n /**\n * Gets the last event's reference id that was submitted to the server.\n * @type {string}\n * @private\n */\n private _lastReferenceId:string = null;\n\n /**\n * Gets the last event's reference id that was submitted to the server.\n * @returns {string}\n */\n getLast(): string {\n return this._lastReferenceId;\n }\n\n /**\n * Clears the last event's reference id.\n */\n clearLast():void {\n this._lastReferenceId = null;\n }\n\n /**\n * Sets the last event's reference id.\n * @param eventId\n */\n setLast(eventId:string):void {\n this._lastReferenceId = eventId;\n }\n}\n\n \n\nexport class ConsoleLog implements ILog {\n public info(message:string):void {\n this.log('info', message);\n }\n\n public warn(message:string):void {\n this.log('warn', message);\n }\n\n public error(message:string):void {\n this.log('error', message);\n }\n\n private log(level:string, message:string) {\n if (console && console[level]) {\n console[level](`[${level}] Exceptionless: ${message}`);\n }\n }\n}\n\n \n\nexport class NullLog implements ILog {\n public info(message:string):void {}\n public warn(message:string):void {}\n public error(message:string):void {}\n}\n\nexport interface IUserInfo {\n identity?:string;\n name?:string;\n data?:any;\n}\n\n \n\nexport interface IEventPlugin {\n priority?:number;\n name?:string;\n run(context:EventPluginContext, next?:() => void): void;\n}\n\n \n\nexport class EventPluginContext {\n public cancelled:boolean;\n public client:ExceptionlessClient;\n public event:IEvent;\n public contextData:ContextData;\n\n constructor(client:ExceptionlessClient, event:IEvent, contextData?:ContextData) {\n this.client = client;\n this.event = event;\n this.contextData = contextData ? contextData : new ContextData();\n }\n\n public get log(): ILog {\n return this.client.config.log;\n }\n}\n\n \n\nexport class EventPluginManager {\n public static run(context:EventPluginContext, callback:(context?:EventPluginContext) => void): void {\n let wrap = function (plugin:IEventPlugin, next?:() => void): () => void {\n return () => {\n try {\n if (!context.cancelled) {\n plugin.run(context, next);\n }\n } catch (ex) {\n context.cancelled = true;\n context.log.error(`Error running plugin '${plugin.name}': ${ex.message}. Discarding Event.`);\n }\n\n if (context.cancelled && !!callback) {\n callback(context);\n }\n };\n };\n\n let plugins:IEventPlugin[] = context.client.config.plugins; // optimization for minifier.\n let wrappedPlugins:{ (): void }[] = [];\n if (!!callback) {\n wrappedPlugins[plugins.length] = wrap({ name: 'cb', priority: 9007199254740992, run: callback }, null);\n }\n\n for (let index = plugins.length - 1; index > -1; index--) {\n wrappedPlugins[index] = wrap(plugins[index], !!callback || (index < plugins.length - 1) ? wrappedPlugins[index + 1] : null);\n }\n\n wrappedPlugins[0]();\n }\n\n public static addDefaultPlugins(config:Configuration): void {\n config.addPlugin(new ConfigurationDefaultsPlugin());\n config.addPlugin(new ErrorPlugin());\n config.addPlugin(new ModuleInfoPlugin());\n config.addPlugin(new RequestInfoPlugin());\n config.addPlugin(new EnvironmentInfoPlugin());\n config.addPlugin(new SubmissionMethodPlugin());\n }\n}\n\n \n\nexport class ReferenceIdPlugin implements IEventPlugin {\n public priority:number = 20;\n public name:string = 'ReferenceIdPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n if ((!context.event.reference_id || context.event.reference_id.length === 0) && context.event.type === 'error') {\n context.event.reference_id = Utils.guid().replace('-', '').substring(0, 10);\n }\n\n next && next();\n }\n}\n\n \n\nexport class DefaultEventQueue implements IEventQueue {\n /**\n * The configuration object.\n * @type {Configuration}\n * @private\n */\n private _config:Configuration;\n\n /**\n * Suspends processing until the specified time.\n * @type {Date}\n * @private\n */\n private _suspendProcessingUntil:Date;\n\n /**\n * Discards queued items until the specified time.\n * @type {Date}\n * @private\n */\n private _discardQueuedItemsUntil:Date;\n\n /**\n * Returns true if the queue is processing.\n * @type {boolean}\n * @private\n */\n private _processingQueue:boolean = false;\n\n /**\n * Processes the queue every xx seconds.\n * @type {Timer}\n * @private\n */\n private _queueTimer:any;\n\n constructor(config:Configuration) {\n this._config = config;\n }\n\n public enqueue(event:IEvent): void {\n let config:Configuration = this._config; // Optimization for minifier.\n this.ensureQueueTimer();\n\n if (this.areQueuedItemsDiscarded()) {\n config.log.info('Queue items are currently being discarded. The event will not be queued.');\n return;\n }\n\n let key = `ex-q-${new Date().toJSON()}-${Utils.randomNumber()}`;\n config.log.info(`Enqueuing event: ${key} type=${event.type} ${!!event.reference_id ? 'refid=' + event.reference_id : ''}`);\n config.storage.save(key, event);\n }\n\n public process(isAppExiting?:boolean): void {\n function getEvents(events:{ path:string, value:IEvent }[]):IEvent[] {\n let items:IEvent[] = [];\n for (let index = 0; index < events.length; index++) {\n items.push(events[index].value);\n }\n\n return items;\n }\n\n const queueNotProcessed:string = 'The queue will not be processed.'; // optimization for minifier.\n let config:Configuration = this._config; // Optimization for minifier.\n let log:ILog = config.log; // Optimization for minifier.\n\n this.ensureQueueTimer();\n\n if (this._processingQueue) {\n return;\n }\n\n log.info('Processing queue...');\n if (!config.enabled) {\n log.info(`Configuration is disabled. ${queueNotProcessed}`);\n return;\n }\n\n if (!config.isValid) {\n log.info(`Invalid Api Key. ${queueNotProcessed}`);\n return;\n }\n\n this._processingQueue = true;\n\n try {\n let events = config.storage.getList('ex-q', config.submissionBatchSize);\n if (!events || events.length === 0) {\n this._processingQueue = false;\n return;\n }\n\n log.info(`Sending ${events.length} events to ${config.serverUrl}.`);\n config.submissionClient.postEvents(getEvents(events), config, (response:SubmissionResponse) => {\n this.processSubmissionResponse(response, events);\n log.info('Finished processing queue.');\n this._processingQueue = false;\n }, isAppExiting);\n } catch (ex) {\n log.error(`Error processing queue: ${ex}`);\n this.suspendProcessing();\n this._processingQueue = false;\n }\n }\n\n public suspendProcessing(durationInMinutes?:number, discardFutureQueuedItems?:boolean, clearQueue?:boolean): void {\n let config:Configuration = this._config; // Optimization for minifier.\n\n if (!durationInMinutes || durationInMinutes <= 0) {\n durationInMinutes = 5;\n }\n\n config.log.info(`Suspending processing for ${durationInMinutes} minutes.`);\n this._suspendProcessingUntil = new Date(new Date().getTime() + (durationInMinutes * 60000));\n\n if (discardFutureQueuedItems) {\n this._discardQueuedItemsUntil = new Date(new Date().getTime() + (durationInMinutes * 60000));\n }\n\n if (clearQueue) {\n // Account is over the limit and we want to ensure that the sample size being sent in will contain newer errors.\n this.removeEvents(config.storage.getList('ex-q'));\n }\n }\n\n private areQueuedItemsDiscarded(): boolean {\n return this._discardQueuedItemsUntil && this._discardQueuedItemsUntil > new Date();\n }\n\n private ensureQueueTimer(): void {\n if (!this._queueTimer) {\n this._queueTimer = setInterval(() => this.onProcessQueue(), 10000);\n }\n }\n\n private isQueueProcessingSuspended(): boolean {\n return this._suspendProcessingUntil && this._suspendProcessingUntil > new Date();\n }\n\n private onProcessQueue(): void {\n if (!this.isQueueProcessingSuspended() && !this._processingQueue) {\n this.process();\n }\n }\n\n private processSubmissionResponse(response:SubmissionResponse, events:{ path:string, value:IEvent }[]): void {\n const noSubmission:string = 'The event will not be submitted.'; // Optimization for minifier.\n let config:Configuration = this._config; // Optimization for minifier.\n let log:ILog = config.log; // Optimization for minifier.\n\n if (response.success) {\n log.info(`Sent ${events.length} events.`);\n this.removeEvents(events);\n return;\n }\n\n if (response.serviceUnavailable) {\n // You are currently over your rate limit or the servers are under stress.\n log.error('Server returned service unavailable.');\n this.suspendProcessing();\n return;\n }\n\n if (response.paymentRequired) {\n // If the organization over the rate limit then discard the event.\n log.info('Too many events have been submitted, please upgrade your plan.');\n this.suspendProcessing(null, true, true);\n return;\n }\n\n if (response.unableToAuthenticate) {\n // The api key was suspended or could not be authorized.\n log.info(`Unable to authenticate, please check your configuration. ${noSubmission}`);\n this.suspendProcessing(15);\n this.removeEvents(events);\n return;\n }\n\n if (response.notFound || response.badRequest) {\n // The service end point could not be found.\n log.error(`Error while trying to submit data: ${response.message}`);\n this.suspendProcessing(60 * 4);\n this.removeEvents(events);\n return;\n }\n\n if (response.requestEntityTooLarge) {\n let message = 'Event submission discarded for being too large.';\n if (config.submissionBatchSize > 1) {\n log.error(`${message} Retrying with smaller batch size.`);\n config.submissionBatchSize = Math.max(1, Math.round(config.submissionBatchSize / 1.5));\n } else {\n log.error(`${message} ${noSubmission}`);\n this.removeEvents(events);\n }\n\n return;\n }\n\n if (!response.success) {\n log.error(`Error submitting events: ${response.message || 'Please check the network tab for more info.'}`);\n this.suspendProcessing();\n }\n }\n\n private removeEvents(events:{ path:string, value:IEvent }[]) {\n for (let index = 0; index < (events || []).length; index++) {\n this._config.storage.remove(events[index].path);\n }\n }\n}\n\n \n\nexport class InMemoryStorage implements IStorage {\n private _items:IStorageItem[] = [];\n private _maxItems:number;\n\n constructor(maxItems?:number) {\n this._maxItems = maxItems > 0 ? maxItems : 250;\n }\n\n public save(path:string, value:T):boolean {\n if (!path || !value) {\n return false;\n }\n\n this.remove(path);\n if (this._items.push({ created: new Date().getTime(), path: path, value: value }) > this._maxItems) {\n this._items.shift();\n }\n\n return true;\n }\n\n public get(path:string):T {\n let item:IStorageItem = path ? this.getList(`^${path}$`, 1)[0] : null;\n return item ? item.value : null;\n }\n\n public getList(searchPattern?:string, limit?:number):IStorageItem[] {\n let items = this._items; // Optimization for minifier\n if (!searchPattern) {\n return items.slice(0, limit);\n }\n\n let regex = new RegExp(searchPattern);\n let results:IStorageItem[] = [];\n for (let index = 0; index < items.length; index++) {\n if (regex.test(items[index].path)) {\n results.push(items[index]);\n\n if (results.length >= limit) {\n break;\n }\n }\n }\n\n return results;\n }\n\n public remove(path:string):void {\n if (path) {\n let item = this.getList(`^${path}$`, 1)[0];\n if (item) {\n this._items.splice(this._items.indexOf(item), 1);\n }\n }\n }\n}\n\n \n\ndeclare var XDomainRequest:{ new (); create(); };\n\nexport class DefaultSubmissionClient implements ISubmissionClient {\n public configurationVersionHeader:string = 'x-exceptionless-configversion';\n\n public postEvents(events:IEvent[], config:Configuration, callback:(response:SubmissionResponse) => void, isAppExiting?:boolean):void {\n let data = Utils.stringify(events, config.dataExclusions);\n let request = this.createRequest(config, 'POST', '/api/v2/events', data);\n let cb = this.createSubmissionCallback(config, callback);\n\n return config.submissionAdapter.sendRequest(request, cb, isAppExiting);\n }\n\n public postUserDescription(referenceId:string, description:IUserDescription, config:Configuration, callback:(response:SubmissionResponse) => void):void {\n let path = `/api/v2/events/by-ref/${encodeURIComponent(referenceId)}/user-description`;\n let data = Utils.stringify(description, config.dataExclusions);\n let request = this.createRequest(config, 'POST', path, data);\n let cb = this.createSubmissionCallback(config, callback);\n\n return config.submissionAdapter.sendRequest(request, cb);\n }\n\n public getSettings(config:Configuration, callback:(response:SettingsResponse) => void):void {\n let request = this.createRequest(config, 'GET', '/api/v2/projects/config');\n let cb = (status, message, data?, headers?) => {\n if (status !== 200) {\n return callback(new SettingsResponse(false, null, -1, null, message));\n }\n\n let settings:IClientConfiguration;\n try {\n settings = JSON.parse(data);\n } catch (e) {\n config.log.error(`Unable to parse settings: '${data}'`);\n }\n\n if (!settings || isNaN(settings.version)) {\n return callback(new SettingsResponse(false, null, -1, null, 'Invalid configuration settings.'));\n }\n\n callback(new SettingsResponse(true, settings.settings || {}, settings.version));\n };\n\n return config.submissionAdapter.sendRequest(request, cb);\n }\n\n private createRequest(config: Configuration, method: string, path: string, data: string = null): SubmissionRequest {\n return {\n method,\n path,\n data,\n serverUrl: config.serverUrl,\n apiKey: config.apiKey,\n userAgent: config.userAgent\n };\n }\n\n private createSubmissionCallback(config:Configuration, callback:(response:SubmissionResponse) => void) {\n return (status, message, data?, headers?) => {\n let settingsVersion:number = headers && parseInt(headers[this.configurationVersionHeader], 10);\n SettingsManager.checkVersion(settingsVersion, config);\n\n callback(new SubmissionResponse(status, message));\n };\n }\n}\n\nexport class Utils {\n public static addRange(target:T[], ...values:T[]) {\n if (!target) {\n target = [];\n }\n\n if (!values || values.length === 0) {\n return target;\n }\n\n for (let index = 0; index < values.length; index++) {\n if (values[index] && target.indexOf(values[index]) < 0) {\n target.push(values[index]);\n }\n }\n\n return target;\n }\n\n public static getHashCode(source:string): string {\n if (!source || source.length === 0) {\n return null;\n }\n\n let hash:number = 0;\n for (let index = 0; index < source.length; index++) {\n let character = source.charCodeAt(index);\n hash = ((hash << 5) - hash) + character;\n hash |= 0;\n }\n\n return hash.toString();\n }\n\n public static getCookies(cookies:string): Object {\n let result:Object = {};\n\n let parts:string[] = (cookies || '').split('; ');\n for (let index = 0; index < parts.length; index++) {\n let cookie:string[] = parts[index].split('=');\n result[cookie[0]] = cookie[1];\n }\n\n return result;\n }\n\n public static guid(): string {\n function s4() {\n return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);\n }\n\n return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();\n }\n\n public static merge(defaultValues:Object, values:Object) {\n let result:Object = {};\n\n for (let key in defaultValues || {}) {\n if (!!defaultValues[key]) {\n result[key] = defaultValues[key];\n }\n }\n\n for (let key in values || {}) {\n if (!!values[key]) {\n result[key] = values[key];\n }\n }\n\n return result;\n }\n\n public static parseVersion(source:string): string {\n if (!source) {\n return null;\n }\n\n let versionRegex = /(v?((\\d+)\\.(\\d+)(\\.(\\d+))?)(?:-([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?(?:\\+([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?)/;\n let matches = versionRegex.exec(source);\n if (matches && matches.length > 0) {\n return matches[0];\n }\n\n return null;\n }\n\n public static parseQueryString(query:string) {\n if (!query || query.length === 0) {\n return null;\n }\n\n let pairs:string[] = query.split('&');\n if (pairs.length === 0) {\n return null;\n }\n\n let result:Object = {};\n for (let index = 0; index < pairs.length; index++) {\n let pair = pairs[index].split('=');\n result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);\n }\n\n return result;\n }\n\n public static randomNumber(): number {\n return Math.floor(Math.random() * 9007199254740992);\n }\n\n public static stringify(data:any, exclusions?:string[]): string {\n function checkForMatch(pattern:string, value:string): boolean {\n if (!pattern || !value || typeof value !== 'string') {\n return false;\n }\n\n let trim = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;\n pattern = pattern.toLowerCase().replace(trim, '');\n value = value.toLowerCase().replace(trim, '');\n\n if (pattern.length <= 0) {\n return false;\n }\n\n let startsWithWildcard:boolean = pattern[0] === '*';\n if (startsWithWildcard) {\n pattern = pattern.slice(1);\n }\n\n let endsWithWildcard:boolean = pattern[pattern.length - 1] === '*';\n if (endsWithWildcard) {\n pattern = pattern.substring(0, pattern.length - 1);\n }\n\n if (startsWithWildcard && endsWithWildcard) {\n return value.indexOf(pattern) !== -1;\n }\n\n if (startsWithWildcard) {\n return value.lastIndexOf(pattern) === (value.length - pattern.length);\n }\n\n if (endsWithWildcard) {\n return value.indexOf(pattern) === 0;\n }\n\n return value === pattern;\n }\n\n function stringifyImpl(obj:any, excludedKeys:string[]): string {\n let cache:string[] = [];\n return JSON.stringify(obj, function(key:string, value:any) {\n for (let index = 0; index < (excludedKeys || []).length; index++) {\n if (checkForMatch(excludedKeys[index], key)) {\n return;\n }\n }\n\n if (typeof value === 'object' && !!value) {\n if (cache.indexOf(value) !== -1) {\n // Circular reference found, discard key\n return;\n }\n\n cache.push(value);\n }\n\n return value;\n });\n }\n\n if (({}).toString.call(data) === '[object Array]') {\n let result = [];\n for (let index = 0; index < data.length; index++) {\n result[index] = JSON.parse(stringifyImpl(data[index], exclusions || []));\n }\n\n return JSON.stringify(result);\n }\n\n return stringifyImpl(data, exclusions || []);\n }\n}\n\n \n\nexport class Configuration implements IConfigurationSettings {\n /**\n * The default configuration settings that are applied to new configuration instances.\n * @type {IConfigurationSettings}\n * @private\n */\n private static _defaultSettings:IConfigurationSettings = null;\n\n /**\n * A default list of tags that will automatically be added to every\n * report submitted to the server.\n *\n * @type {Array}\n */\n public defaultTags:string[] = [];\n\n /**\n * A default list of of extended data objects that will automatically\n * be added to every report submitted to the server.\n *\n * @type {{}}\n */\n public defaultData:Object = {};\n\n /**\n * Whether the client is currently enabled or not. If it is disabled,\n * submitted errors will be discarded and no data will be sent to the server.\n *\n * @returns {boolean}\n */\n public enabled:boolean = true;\n\n public environmentInfoCollector:IEnvironmentInfoCollector;\n public errorParser:IErrorParser;\n public lastReferenceIdManager:ILastReferenceIdManager = new DefaultLastReferenceIdManager();\n public log:ILog;\n public moduleCollector:IModuleCollector;\n public requestInfoCollector:IRequestInfoCollector;\n\n /**\n * Maximum number of events that should be sent to the server together in a batch. (Defaults to 50)\n */\n public submissionBatchSize:number;\n public submissionAdapter:ISubmissionAdapter;\n public submissionClient:ISubmissionClient;\n\n /**\n * Contains a dictionary of custom settings that can be used to control\n * the client and will be automatically updated from the server.\n */\n public settings:Object = {};\n\n public storage:IStorage;\n\n public queue:IEventQueue;\n\n /**\n * The list of plugins that will be used in this configuration.\n * @type {Array}\n * @private\n */\n private _plugins:IEventPlugin[] = [];\n\n constructor(configSettings?:IConfigurationSettings) {\n function inject(fn:any) {\n return typeof fn === 'function' ? fn(this) : fn;\n }\n\n configSettings = Utils.merge(Configuration.defaults, configSettings);\n\n this.log = inject(configSettings.log) || new NullLog();\n this.apiKey = configSettings.apiKey;\n this.serverUrl = configSettings.serverUrl;\n\n this.environmentInfoCollector = inject(configSettings.environmentInfoCollector);\n this.errorParser = inject(configSettings.errorParser);\n this.lastReferenceIdManager = inject(configSettings.lastReferenceIdManager) || new DefaultLastReferenceIdManager();\n this.moduleCollector = inject(configSettings.moduleCollector);\n this.requestInfoCollector = inject(configSettings.requestInfoCollector);\n this.submissionBatchSize = inject(configSettings.submissionBatchSize) || 50;\n this.submissionAdapter = inject(configSettings.submissionAdapter);\n this.submissionClient = inject(configSettings.submissionClient) || new DefaultSubmissionClient();\n this.storage = inject(configSettings.storage) || new InMemoryStorage();\n this.queue = inject(configSettings.queue) || new DefaultEventQueue(this);\n\n SettingsManager.applySavedServerSettings(this);\n EventPluginManager.addDefaultPlugins(this);\n }\n\n /**\n * The API key that will be used when sending events to the server.\n * @type {string}\n * @private\n */\n private _apiKey:string;\n\n /**\n * The API key that will be used when sending events to the server.\n * @returns {string}\n */\n public get apiKey():string {\n return this._apiKey;\n }\n\n /**\n * The API key that will be used when sending events to the server.\n * @param value\n */\n public set apiKey(value:string) {\n this._apiKey = value || null;\n this.log.info(`apiKey: ${this._apiKey}`);\n }\n\n /**\n * Returns true if the apiKey is valid.\n * @returns {boolean}\n */\n public get isValid():boolean {\n return !!this.apiKey && this.apiKey.length >= 10;\n }\n\n /**\n * The server url that all events will be sent to.\n * @type {string}\n * @private\n */\n private _serverUrl:string = 'https://collector.exceptionless.io';\n\n /**\n * The server url that all events will be sent to.\n * @returns {string}\n */\n public get serverUrl():string {\n return this._serverUrl;\n }\n\n /**\n * The server url that all events will be sent to.\n * @param value\n */\n public set serverUrl(value:string) {\n if (!!value) {\n this._serverUrl = value;\n this.log.info(`serverUrl: ${this._serverUrl}`);\n }\n }\n\n /**\n * A list of exclusion patterns.\n * @type {Array}\n * @private\n */\n private _dataExclusions:string[] = [];\n\n /**\n * A list of exclusion patterns that will automatically remove any data that\n * matches them from any data submitted to the server.\n *\n * For example, entering CreditCard will remove any extended data properties,\n * form fields, cookies and query parameters from the report.\n *\n * @returns {string[]}\n */\n public get dataExclusions():string[] {\n let exclusions:string = this.settings['@@DataExclusions'];\n return this._dataExclusions.concat(exclusions && exclusions.split(',') || []);\n }\n\n /**\n * Add items to the list of exclusion patterns that will automatically remove any\n * data that matches them from any data submitted to the server.\n *\n * For example, entering CreditCard will remove any extended data properties, form\n * fields, cookies and query parameters from the report.\n *\n * @param exclusions\n */\n public addDataExclusions(...exclusions:string[]) {\n this._dataExclusions = Utils.addRange(this._dataExclusions, ...exclusions);\n }\n\n /**\n * The list of plugins that will be used in this configuration.\n * @returns {IEventPlugin[]}\n */\n public get plugins():IEventPlugin[] {\n return this._plugins.sort((p1:IEventPlugin, p2:IEventPlugin) => {\n return (p1.priority < p2.priority) ? -1 : (p1.priority > p2.priority) ? 1 : 0;\n });\n }\n\n /**\n * Register an plugin to be used in this configuration.\n * @param plugin\n */\n public addPlugin(plugin:IEventPlugin): void;\n\n /**\n * Register an plugin to be used in this configuration.\n * @param name The name used to identify the plugin.\n * @param priority Used to determine plugins priority.\n * @param pluginAction A function that is run.\n */\n public addPlugin(name:string, priority:number, pluginAction:(context:EventPluginContext, next?:() => void) => void): void;\n public addPlugin(pluginOrName:IEventPlugin|string, priority?:number, pluginAction?:(context:EventPluginContext, next?:() => void) => void): void {\n let plugin:IEventPlugin = !!pluginAction ? { name: pluginOrName, priority: priority, run: pluginAction } : pluginOrName;\n if (!plugin || !plugin.run) {\n this.log.error('Add plugin failed: Run method not defined');\n return;\n }\n\n if (!plugin.name) {\n plugin.name = Utils.guid();\n }\n\n if (!plugin.priority) {\n plugin.priority = 0;\n }\n\n let pluginExists:boolean = false;\n let plugins = this._plugins; // optimization for minifier.\n for (let index = 0; index < plugins.length; index++) {\n if (plugins[index].name === plugin.name) {\n pluginExists = true;\n break;\n }\n }\n\n if (!pluginExists) {\n plugins.push(plugin);\n }\n }\n\n /**\n * Remove the plugin from this configuration.\n * @param plugin\n */\n public removePlugin(plugin:IEventPlugin): void;\n\n /**\n * Remove an plugin by key from this configuration.\n * @param name\n */\n public removePlugin(name:string): void;\n public removePlugin(pluginOrName:IEventPlugin|string): void {\n let name:string = typeof pluginOrName === 'string' ? pluginOrName : pluginOrName.name;\n if (!name) {\n this.log.error('Remove plugin failed: Plugin name not defined');\n return;\n }\n\n let plugins = this._plugins; // optimization for minifier.\n for (let index = 0; index < plugins.length; index++) {\n if (plugins[index].name === name) {\n plugins.splice(index, 1);\n break;\n }\n }\n }\n\n /**\n * Automatically set the application version for events.\n * @param version\n */\n public setVersion(version:string): void {\n if (!!version) {\n this.defaultData['@version'] = version;\n }\n }\n\n public setUserIdentity(userInfo:IUserInfo): void;\n public setUserIdentity(identity:string): void;\n public setUserIdentity(identity:string, name:string): void;\n public setUserIdentity(userInfoOrIdentity:IUserInfo|string, name?:string): void {\n const USER_KEY:string = '@user'; // optimization for minifier.\n let userInfo:IUserInfo = typeof userInfoOrIdentity !== 'string' ? userInfoOrIdentity : { identity: userInfoOrIdentity, name: name };\n\n let shouldRemove:boolean = !userInfo || (!userInfo.identity && !userInfo.name);\n if (shouldRemove) {\n delete this.defaultData[USER_KEY];\n } else {\n this.defaultData[USER_KEY] = userInfo;\n }\n\n this.log.info(`user identity: ${shouldRemove ? 'null' : userInfo.identity}`);\n }\n\n /**\n * Used to identify the client that sent the events to the server.\n * @returns {string}\n */\n public get userAgent():string {\n return 'exceptionless-js/1.0.0.0';\n }\n\n /**\n * Automatically set a reference id for error events.\n */\n public useReferenceIds(): void {\n this.addPlugin(new ReferenceIdPlugin());\n }\n\n // TODO: Support a min log level.\n public useDebugLogger(): void {\n this.log = new ConsoleLog();\n }\n\n /**\n * The default configuration settings that are applied to new configuration instances.\n * @returns {IConfigurationSettings}\n */\n public static get defaults() {\n if (Configuration._defaultSettings === null) {\n Configuration._defaultSettings = {};\n }\n\n return Configuration._defaultSettings;\n }\n}\n\n \n\nexport class EventBuilder {\n public target:IEvent;\n public client:ExceptionlessClient;\n public pluginContextData:ContextData;\n\n private _validIdentifierErrorMessage:string = 'must contain between 8 and 100 alphanumeric or \\'-\\' characters.'; // optimization for minifier.\n\n constructor(event:IEvent, client:ExceptionlessClient, pluginContextData?:ContextData) {\n this.target = event;\n this.client = client;\n this.pluginContextData = pluginContextData || new ContextData();\n }\n\n public setType(type:string): EventBuilder {\n if (!!type) {\n this.target.type = type;\n }\n\n return this;\n }\n\n public setSource(source:string): EventBuilder {\n if (!!source) {\n this.target.source = source;\n }\n\n return this;\n }\n\n public setSessionId(sessionId:string): EventBuilder {\n if (!this.isValidIdentifier(sessionId)) {\n throw new Error(`SessionId ${this._validIdentifierErrorMessage}`);\n }\n\n this.target.session_id = sessionId;\n return this;\n }\n\n public setReferenceId(referenceId:string): EventBuilder {\n if (!this.isValidIdentifier(referenceId)) {\n throw new Error(`ReferenceId ${this._validIdentifierErrorMessage}`);\n }\n\n this.target.reference_id = referenceId;\n return this;\n }\n\n public setMessage(message:string): EventBuilder {\n if (!!message) {\n this.target.message = message;\n }\n\n return this;\n }\n\n public setGeo(latitude: number, longitude: number): EventBuilder {\n if (latitude < -90.0 || latitude > 90.0) {\n throw new Error('Must be a valid latitude value between -90.0 and 90.0.');\n }\n\n if (longitude < -180.0 || longitude > 180.0) {\n throw new Error('Must be a valid longitude value between -180.0 and 180.0.');\n }\n\n this.target.geo = `${latitude},${longitude}`;\n return this;\n }\n\n public setUserIdentity(userInfo:IUserInfo): EventBuilder;\n public setUserIdentity(identity:string): EventBuilder;\n public setUserIdentity(identity:string, name:string): EventBuilder;\n public setUserIdentity(userInfoOrIdentity:IUserInfo|string, name?:string): EventBuilder {\n let userInfo = typeof userInfoOrIdentity !== 'string' ? userInfoOrIdentity : { identity: userInfoOrIdentity, name: name };\n if (!userInfo || (!userInfo.identity && !userInfo.name)) {\n return this;\n }\n\n this.setProperty('@user', userInfo);\n return this;\n }\n\n public setValue(value:number): EventBuilder {\n if (!!value) {\n this.target.value = value;\n }\n\n return this;\n }\n\n public addTags(...tags:string[]): EventBuilder {\n this.target.tags = Utils.addRange(this.target.tags, ...tags);\n return this;\n }\n\n public setProperty(name:string, value:any): EventBuilder {\n if (!name || (value === undefined || value == null)) {\n return this;\n }\n\n if (!this.target.data) {\n this.target.data = {};\n }\n\n this.target.data[name] = value;\n return this;\n }\n\n public markAsCritical(critical:boolean): EventBuilder {\n if (critical) {\n this.addTags('Critical');\n }\n\n return this;\n }\n\n public addRequestInfo(request:Object): EventBuilder {\n if (!!request) {\n this.pluginContextData['@request'] = request;\n }\n\n return this;\n }\n\n public submit(callback?:(context:EventPluginContext) => void): void {\n this.client.submitEvent(this.target, this.pluginContextData, callback);\n }\n\n private isValidIdentifier(value:string): boolean {\n if (!value) {\n return true;\n }\n\n if (value.length < 8 || value.length > 100) {\n return false;\n }\n\n for (var index = 0; index < value.length; index++) {\n let code = value.charCodeAt(index);\n let isDigit = (code >= 48) && (code <= 57);\n let isLetter = ((code >= 65) && (code <= 90)) || ((code >= 97) && (code <= 122));\n let isMinus = code === 45;\n\n if (!(isDigit || isLetter) && !isMinus) {\n return false;\n }\n }\n\n return true;\n }\n}\n\nexport interface IUserDescription {\n email_address?:string;\n description?:string;\n data?:any;\n}\n\nexport class ContextData {\n public setException(exception:Error): void {\n if (exception) {\n this['@@_Exception'] = exception;\n }\n }\n\n public get hasException(): boolean {\n return !!this['@@_Exception'];\n }\n\n public getException(): Error {\n return this['@@_Exception'] || null;\n }\n\n public markAsUnhandledError(): void {\n this['@@_IsUnhandledError'] = true;\n }\n\n public get isUnhandledError(): boolean {\n return !!this['@@_IsUnhandledError'];\n }\n\n public setSubmissionMethod(method:string): void {\n if (method) {\n this['@@_SubmissionMethod'] = method;\n }\n }\n\n public getSubmissionMethod(): string {\n return this['@@_SubmissionMethod'] || null;\n }\n}\n\nexport class SubmissionResponse {\n success:boolean = false;\n badRequest:boolean = false;\n serviceUnavailable:boolean = false;\n paymentRequired:boolean = false;\n unableToAuthenticate:boolean = false;\n notFound:boolean = false;\n requestEntityTooLarge:boolean = false;\n statusCode:number;\n message:string;\n\n constructor(statusCode:number, message?:string) {\n this.statusCode = statusCode;\n this.message = message;\n\n this.success = statusCode >= 200 && statusCode <= 299;\n this.badRequest = statusCode === 400;\n this.serviceUnavailable = statusCode === 503;\n this.paymentRequired = statusCode === 402;\n this.unableToAuthenticate = statusCode === 401 || statusCode === 403;\n this.notFound = statusCode === 404;\n this.requestEntityTooLarge = statusCode === 413;\n }\n}\n\n \n\nexport class ExceptionlessClient {\n /**\n * The default ExceptionlessClient instance.\n * @type {ExceptionlessClient}\n * @private\n */\n private static _instance:ExceptionlessClient = null;\n\n public config:Configuration;\n\n constructor();\n constructor(settings:IConfigurationSettings);\n constructor(apiKey:string, serverUrl?:string);\n constructor(settingsOrApiKey?:IConfigurationSettings|string, serverUrl?:string) {\n if (typeof settingsOrApiKey !== 'object') {\n this.config = new Configuration(settingsOrApiKey);\n } else {\n this.config = new Configuration({ apiKey: settingsOrApiKey, serverUrl: serverUrl });\n }\n }\n\n public createException(exception:Error): EventBuilder {\n let pluginContextData = new ContextData();\n pluginContextData.setException(exception);\n return this.createEvent(pluginContextData).setType('error');\n }\n\n public submitException(exception:Error, callback?:(context:EventPluginContext) => void): void {\n this.createException(exception).submit(callback);\n }\n\n public createUnhandledException(exception:Error, submissionMethod?:string): EventBuilder {\n let builder = this.createException(exception);\n builder.pluginContextData.markAsUnhandledError();\n builder.pluginContextData.setSubmissionMethod(submissionMethod);\n\n return builder;\n }\n\n public submitUnhandledException(exception:Error, submissionMethod?:string, callback?:(context:EventPluginContext) => void) {\n this.createUnhandledException(exception, submissionMethod).submit(callback);\n }\n\n public createFeatureUsage(feature:string): EventBuilder {\n return this.createEvent().setType('usage').setSource(feature);\n }\n\n public submitFeatureUsage(feature:string, callback?:(context:EventPluginContext) => void): void {\n this.createFeatureUsage(feature).submit(callback);\n }\n\n public createLog(message:string): EventBuilder;\n public createLog(source:string, message:string): EventBuilder;\n public createLog(source:string, message:string, level:string): EventBuilder;\n public createLog(sourceOrMessage:string, message?:string, level?:string): EventBuilder {\n let builder = this.createEvent().setType('log');\n\n if (message && level) {\n builder = builder.setSource(sourceOrMessage).setMessage(message).setProperty('@level', level);\n } else if (message) {\n builder = builder.setSource(sourceOrMessage).setMessage(message);\n } else {\n // TODO: Look into using https://www.stevefenton.co.uk/Content/Blog/Date/201304/Blog/Obtaining-A-Class-Name-At-Runtime-In-TypeScript/\n let caller:any = arguments.callee.caller;\n builder = builder.setSource(caller && caller.name).setMessage(sourceOrMessage);\n }\n\n return builder;\n }\n\n public submitLog(message:string): void;\n public submitLog(source:string, message:string): void;\n public submitLog(source:string, message:string, level:string, callback?:(context:EventPluginContext) => void): void;\n public submitLog(sourceOrMessage:string, message?:string, level?:string, callback?:(context:EventPluginContext) => void): void {\n this.createLog(sourceOrMessage, message, level).submit(callback);\n }\n\n public createNotFound(resource:string): EventBuilder {\n return this.createEvent().setType('404').setSource(resource);\n }\n\n public submitNotFound(resource:string, callback?:(context:EventPluginContext) => void): void {\n this.createNotFound(resource).submit(callback);\n }\n\n public createSessionStart(sessionId:string): EventBuilder {\n return this.createEvent().setType('start').setSessionId(sessionId);\n }\n\n public submitSessionStart(sessionId:string, callback?:(context:EventPluginContext) => void): void {\n this.createSessionStart(sessionId).submit(callback);\n }\n\n public createSessionEnd(sessionId:string): EventBuilder {\n return this.createEvent().setType('end').setSessionId(sessionId);\n }\n\n public submitSessionEnd(sessionId:string, callback?:(context:EventPluginContext) => void): void {\n this.createSessionEnd(sessionId).submit(callback);\n }\n\n public createEvent(pluginContextData?:ContextData): EventBuilder {\n return new EventBuilder({ date: new Date() }, this, pluginContextData);\n }\n\n /**\n * Submits the event to be sent to the server.\n * @param event The event data.\n * @param pluginContextData Any contextual data objects to be used by Exceptionless plugins to gather default information for inclusion in the report information.\n * @param callback\n */\n public submitEvent(event:IEvent, pluginContextData?:ContextData, callback?:(context:EventPluginContext) => void): void {\n function cancelled(context:EventPluginContext) {\n if (!!context) {\n context.cancelled = true;\n }\n\n return !!callback && callback(context);\n }\n\n let context = new EventPluginContext(this, event, pluginContextData);\n if (!event) {\n return cancelled(context);\n }\n\n if (!this.config.enabled) {\n this.config.log.info('Event submission is currently disabled.');\n return cancelled(context);\n }\n\n if (!event.data) {\n event.data = {};\n }\n\n if (!event.tags || !event.tags.length) {\n event.tags = [];\n }\n\n EventPluginManager.run(context, function (ctx:EventPluginContext) {\n let ev = ctx.event;\n if (!ctx.cancelled) {\n // ensure all required data\n if (!ev.type || ev.type.length === 0) {\n ev.type = 'log';\n }\n\n if (!ev.date) {\n ev.date = new Date();\n }\n\n let config = ctx.client.config;\n config.queue.enqueue(ev);\n\n if (ev.reference_id && ev.reference_id.length > 0) {\n ctx.log.info(`Setting last reference id '${ev.reference_id}'`);\n config.lastReferenceIdManager.setLast(ev.reference_id);\n }\n }\n\n !!callback && callback(ctx);\n });\n }\n\n /**\n * Updates the user's email address and description of an event for the specified reference id.\n * @param referenceId The reference id of the event to update.\n * @param email The user's email address to set on the event.\n * @param description The user's description of the event.\n */\n public updateUserEmailAndDescription(referenceId:string, email:string, description:string, callback?:(response:SubmissionResponse) => void) {\n if (!referenceId || !email || !description || !this.config.enabled) {\n return !!callback && callback(new SubmissionResponse(500, 'cancelled'));\n }\n\n let userDescription:IUserDescription = { email_address: email, description: description };\n this.config.submissionClient.postUserDescription(referenceId, userDescription, this.config, (response:SubmissionResponse) => {\n if (!response.success) {\n this.config.log.error(`Failed to submit user email and description for event '${referenceId}': ${response.statusCode} ${response.message}`);\n }\n\n !!callback && callback(response);\n });\n }\n\n /**\n * Gets the last event client id that was submitted to the server.\n * @returns {string} The event client id.\n */\n public getLastReferenceId(): string {\n return this.config.lastReferenceIdManager.getLast();\n }\n\n /**\n * The default ExceptionlessClient instance.\n * @type {ExceptionlessClient}\n */\n public static get default() {\n if (ExceptionlessClient._instance === null) {\n ExceptionlessClient._instance = new ExceptionlessClient(null);\n }\n\n return ExceptionlessClient._instance;\n }\n}\n\nexport interface IModule {\n data?:any;\n\n module_id?:number;\n name?:string;\n version?:string;\n is_entry?:boolean;\n created_date?:Date;\n modified_date?:Date;\n}\n\nexport interface IRequestInfo {\n user_agent?:string;\n http_method?:string;\n is_secure?:boolean;\n host?:string;\n port?:number;\n path?:string;\n referrer?:string;\n client_ip_address?:string;\n cookies?:any;\n post_data?:any;\n query_string?:any;\n data?:any;\n}\n\nexport interface IEnvironmentInfo {\n processor_count?:number;\n total_physical_memory?:number;\n available_physical_memory?:number;\n command_line?:string;\n process_name?:string;\n process_id?:string;\n process_memory_size?:number;\n thread_id?:string;\n architecture?:string;\n o_s_name?:string;\n o_s_version?:string;\n ip_address?:string;\n machine_name?:string;\n install_id?:string;\n runtime_version?:string;\n data?:any;\n}\n\n \n\nexport class ConfigurationDefaultsPlugin implements IEventPlugin {\n public priority:number = 10;\n public name:string = 'ConfigurationDefaultsPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n let defaultTags:string[] = context.client.config.defaultTags || [];\n for (let index = 0; index < defaultTags.length; index++) {\n let tag = defaultTags[index];\n if (!!tag && context.event.tags.indexOf(tag) < 0) {\n context.event.tags.push(tag);\n }\n }\n\n let defaultData:Object = context.client.config.defaultData || {};\n for (let key in defaultData) {\n if (!!defaultData[key]) {\n context.event.data[key] = defaultData[key];\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class ErrorPlugin implements IEventPlugin {\n public priority:number = 30;\n public name:string = 'ErrorPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ERROR_KEY:string = '@error'; // optimization for minifier.\n\n let exception = context.contextData.getException();\n if (!!exception) {\n context.event.type = 'error';\n\n if (!context.event.data[ERROR_KEY]) {\n let parser = context.client.config.errorParser;\n if (!parser) {\n throw new Error('No error parser was defined.');\n }\n\n let result = parser.parse(context, exception);\n if (!!result) {\n context.event.data[ERROR_KEY] = result;\n }\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class ModuleInfoPlugin implements IEventPlugin {\n public priority:number = 40;\n public name:string = 'ModuleInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ERROR_KEY:string = '@error'; // optimization for minifier.\n\n let collector = context.client.config.moduleCollector;\n if (context.event.data[ERROR_KEY] && !context.event.data['@error'].modules && !!collector) {\n let modules:IModule[] = collector.getModules(context);\n if (modules && modules.length > 0) {\n context.event.data[ERROR_KEY].modules = modules;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class RequestInfoPlugin implements IEventPlugin {\n public priority:number = 60;\n public name:string = 'RequestInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const REQUEST_KEY:string = '@request'; // optimization for minifier.\n\n let collector = context.client.config.requestInfoCollector;\n if (!context.event.data[REQUEST_KEY] && !!collector) {\n let requestInfo:IRequestInfo = collector.getRequestInfo(context);\n if (!!requestInfo) {\n context.event.data[REQUEST_KEY] = requestInfo;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class EnvironmentInfoPlugin implements IEventPlugin {\n public priority:number = 70;\n public name:string = 'EnvironmentInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ENVIRONMENT_KEY:string = '@environment'; // optimization for minifier.\n\n let collector = context.client.config.environmentInfoCollector;\n if (!context.event.data[ENVIRONMENT_KEY] && collector) {\n let environmentInfo:IEnvironmentInfo = collector.getEnvironmentInfo(context);\n if (!!environmentInfo) {\n context.event.data[ENVIRONMENT_KEY] = environmentInfo;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class SubmissionMethodPlugin implements IEventPlugin {\n public priority:number = 100;\n public name:string = 'SubmissionMethodPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n let submissionMethod:string = context.contextData.getSubmissionMethod();\n if (!!submissionMethod) {\n context.event.data['@submission_method'] = submissionMethod;\n }\n\n next && next();\n }\n}\n\nexport interface IParameter {\n data?:any;\n generic_arguments?:string[];\n\n name?:string;\n type?:string;\n type_namespace?:string;\n}\n\n \n\nexport interface IMethod {\n data?:any;\n generic_arguments?:string[];\n parameters?:IParameter[];\n\n is_signature_target?:boolean;\n declaring_namespace?:string;\n declaring_type?:string;\n name?:string;\n module_id?:number;\n}\n\n \n\nexport interface IStackFrame extends IMethod {\n file_name?:string;\n line_number?:number;\n column?:number;\n}\n\n \n\nexport interface IInnerError {\n message?:string;\n type?:string;\n code?:string;\n data?:any;\n inner?:IInnerError;\n stack_trace?:IStackFrame[];\n target_method?:IMethod;\n}\n\n \n\nexport interface IError extends IInnerError {\n modules?:IModule[];\n}\n\nexport interface IStorageItem {\n created:number;\n path:string;\n value:T;\n}\n\nexport interface SubmissionCallback {\n (status: number, message: string, data?: string, headers?: Object): void;\n}\n\nexport interface SubmissionRequest {\n serverUrl: string;\n apiKey: string;\n userAgent: string;\n method: string;\n path: string;\n data: string;\n}\n\nexport class SettingsResponse {\n success:boolean = false;\n settings:any;\n settingsVersion:number = -1;\n message:string;\n exception:any;\n\n constructor(success:boolean, settings:any, settingsVersion:number = -1, exception:any = null, message:string = null) {\n this.success = success;\n this.settings = settings;\n this.settingsVersion = settingsVersion;\n this.exception = exception;\n this.message = message;\n }\n}\n\nexport interface IClientConfiguration {\n settings:Object;\n version:number;\n}\n\nimport os = require('os');\nimport nodestacktrace = require('stack-trace');\nimport https = require('https');\nimport url = require('url');\n \n\nexport class NodeEnvironmentInfoCollector implements IEnvironmentInfoCollector {\n public getEnvironmentInfo(context:EventPluginContext): IEnvironmentInfo {\n function getIpAddresses():string {\n let ips:string[] = [];\n let interfaces = os.networkInterfaces();\n Object.keys(interfaces).forEach((name) => {\n interfaces[name].forEach((iface:any) => {\n if ('IPv4' === iface.family && !iface.internal) {\n ips.push(iface.address);\n }\n });\n });\n\n return ips.join(', ');\n }\n\n if (!os) {\n return null;\n }\n\n let environmentInfo: IEnvironmentInfo = {\n processor_count: os.cpus().length,\n total_physical_memory: os.totalmem(),\n available_physical_memory: os.freemem(),\n command_line: process.argv.join(' '),\n process_name: (process.title || '').replace(/[\\uE000-\\uF8FF]/g, ''),\n process_id: process.pid + '',\n process_memory_size: process.memoryUsage().heapTotal,\n // thread_id: '',\n architecture: os.arch(),\n o_s_name: os.type(),\n o_s_version: os.release(),\n ip_address: getIpAddresses(),\n machine_name: os.hostname(),\n // install_id: '',\n runtime_version: process.version,\n data: {\n loadavg: os.loadavg(),\n platform: os.platform(),\n tmpdir: os.tmpdir(),\n uptime: os.uptime()\n }\n };\n\n if ((os).endianness) {\n environmentInfo.data.endianness = (os).endianness();\n }\n\n return environmentInfo;\n }\n}\n\n \n\nexport class NodeErrorParser implements IErrorParser {\n public parse(context:EventPluginContext, exception:Error): IError {\n function getStackFrames(stackFrames:any[]): IStackFrame[] {\n let frames:IStackFrame[] = [];\n\n for (let index = 0; index < stackFrames.length; index++) {\n let frame = stackFrames[index];\n frames.push({\n name: frame.getMethodName() || frame.getFunctionName(),\n // parameters: frame.args,\n file_name: frame.getFileName(),\n line_number: frame.getLineNumber() || 0,\n column: frame.getColumnNumber() || 0,\n declaring_type: frame.getTypeName(),\n data: {\n is_native: frame.isNative() || (!!frame.filename && frame.filename[0] !== '/' && frame.filename[0] !== '.')\n }\n });\n }\n\n return frames;\n }\n\n if (!nodestacktrace) {\n throw new Error('Unable to load the stack trace library.');\n }\n\n let stackFrames = nodestacktrace.parse(exception) || [];\n return {\n type: exception.name,\n message: exception.message,\n stack_trace: getStackFrames(stackFrames)\n };\n }\n}\n\n \n\nexport class NodeRequestInfoCollector implements IRequestInfoCollector {\n getRequestInfo(context:EventPluginContext):IRequestInfo {\n const REQUEST_KEY:string = '@request'; // optimization for minifier.\n if (!context.contextData[REQUEST_KEY]) {\n return null;\n }\n\n let request = context.contextData[REQUEST_KEY];\n // TODO: include referrer\n let requestInfo:IRequestInfo = {\n client_ip_address: request.ip,\n user_agent: request.headers['user-agent'],\n is_secure: request.secure,\n http_method: request.method,\n host: request.hostname || request.host,\n path: request.path,\n post_data: request.body,\n cookies: Utils.getCookies((request || {}).headers.cookie),\n query_string: request.params\n };\n\n let host = request.headers.host;\n let port:number = host && parseInt(host.slice(host.indexOf(':') + 1), 10);\n if (port > 0) {\n requestInfo.port = port;\n }\n\n return requestInfo;\n }\n}\n\n \n\nexport class NodeSubmissionAdapter implements ISubmissionAdapter {\n public sendRequest(request:SubmissionRequest, callback:SubmissionCallback, isAppExiting?:boolean) {\n if (isAppExiting) {\n this.sendRequestSync(request, callback);\n return;\n }\n\n let parsedHost = url.parse(request.serverUrl);\n\n let options: https.RequestOptions = {\n auth: `client:${request.apiKey}`,\n headers: {},\n hostname: parsedHost.hostname,\n method: request.method,\n port: parsedHost.port && parseInt(parsedHost.port, 10),\n path: request.path\n };\n\n options.headers['User-Agent'] = request.userAgent;\n\n if (request.method === 'POST') {\n options.headers = {\n 'Content-Type': 'application/json',\n 'Content-Length': request.data.length\n };\n }\n\n let protocol = (parsedHost.protocol === 'https' ? https : http);\n let clientRequest: http.ClientRequest = protocol.request(options, (response: http.IncomingMessage) => {\n let body = '';\n response.setEncoding('utf8');\n response.on('data', (chunk) => body += chunk);\n response.on('end', () => this.complete(response, body, response.headers, callback));\n });\n\n clientRequest.on('error', (error: Error) => callback(500, error.message));\n clientRequest.end(request.data);\n }\n\n private complete(response: http.IncomingMessage, responseBody: string, responseHeaders: Object, callback: SubmissionCallback): void {\n let message: string;\n if (response.statusCode === 0) {\n message = 'Unable to connect to server.';\n } else if (response.statusCode < 200 || response.statusCode > 299) {\n message = response.statusMessage || (response).message;\n }\n\n callback(response.statusCode || 500, message, responseBody, responseHeaders);\n }\n\n private sendRequestSync(request: SubmissionRequest, callback: SubmissionCallback): void {\n let requestJson = JSON.stringify(request);\n let res = child.spawnSync(process.execPath, [require.resolve('./submitSync.js')],\n {\n input: requestJson,\n stdio: ['pipe', 'pipe', process.stderr]\n });\n\n let out = res.stdout.toString();\n let result = JSON.parse(out);\n\n callback(result.status, result.message, result.data, result.headers);\n }\n}\n\n \n\nconst EXIT: string = 'exit';\nconst UNCAUGHT_EXCEPTION: string = 'uncaughtException';\nconst SIGINT: string = 'SIGINT';\nconst SIGINT_CODE: number = 2;\n\nlet defaults = Configuration.defaults;\ndefaults.environmentInfoCollector = new NodeEnvironmentInfoCollector();\ndefaults.errorParser = new NodeErrorParser();\ndefaults.requestInfoCollector = new NodeRequestInfoCollector();\ndefaults.submissionAdapter = new NodeSubmissionAdapter();\n\nfunction getListenerCount(emitter, event:string): number {\n if (emitter.listenerCount) {\n return emitter.listenerCount(event);\n }\n return require('events').listenerCount(emitter, event);\n}\n\n/*\n * Adding a event handler for 'uncaughtException' modifies the default\n * Node behavior, so it won't exit or log to the console. Instead,\n * we hijack the event emitter and forward the exception to the callback.\n */\nfunction onUncaughtException(callback: (error: Error) => void) {\n let originalEmit = process.emit;\n\n process.emit = function(type: string, error: Error) {\n if (type === UNCAUGHT_EXCEPTION) {\n callback(error);\n }\n\n return originalEmit.apply(this, arguments);\n };\n}\n\nonUncaughtException(function(error: Error) {\n ExceptionlessClient.default.submitUnhandledException(error, UNCAUGHT_EXCEPTION);\n});\n\n/*\n * We cannot hijack SIGINT, so if there are no other handlers,\n * we just reproduce default Node.js behavior by exiting.\n */\nprocess.on(SIGINT, function() {\n if (getListenerCount(process, SIGINT) <= 1) {\n process.exit(128 + SIGINT_CODE);\n }\n});\n\nprocess.on(EXIT, function(code: number) {\n /**\n * exit codes: https://nodejs.org/api/process.html#process_event_exit\n * From now on, only synchronous code may run. As soon as this method\n * ends, the application inevitably will exit.\n */\n function getExitCodeReason(exitCode: number): string {\n if (exitCode === 1) {\n return 'Uncaught Fatal Exception';\n }\n\n if (exitCode === 3) {\n return 'Internal JavaScript Parse Error';\n }\n\n if (exitCode === 4) {\n return 'Internal JavaScript Evaluation Failure';\n }\n\n if (exitCode === 5) {\n return 'Fatal Exception';\n }\n\n if (exitCode === 6) {\n return 'Non-function Internal Exception Handler ';\n }\n\n if (exitCode === 7) {\n return 'Internal Exception Handler Run-Time Failure';\n }\n\n if (exitCode === 8) {\n return 'Uncaught Exception';\n }\n\n if (exitCode === 9) {\n return 'Invalid Argument';\n }\n\n if (exitCode === 10) {\n return 'Internal JavaScript Run-Time Failure';\n }\n\n if (exitCode === 12) {\n return 'Invalid Debug Argument';\n }\n\n return null;\n }\n\n let client = ExceptionlessClient.default;\n let message = getExitCodeReason(code);\n\n if (message !== null) {\n client.submitLog(EXIT, message, 'Error');\n }\n\n client.config.queue.process(true);\n // Application will now exit.\n});\n\n(Error).stackTraceLimit = Infinity;\n\n"]} \ No newline at end of file +{"version":3,"file":"exceptionless.node.js","sourceRoot":"/source/","sources":["exceptionless.node.ts"],"names":["getListenerCount","onUncaughtException","getExitCodeReason"],"mappings":"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","sourcesContent":["import * as events from \"events\";\nimport * as net from \"net\";\nimport * as stream from \"stream\";\nimport * as child from \"child_process\";\nimport * as tls from \"tls\";\nimport * as http from \"http\";\nimport * as crypto from \"crypto\";\nexport interface IEvent {\n type?:string;\n source?:string;\n date?:Date;\n tags?:string[];\n message?:string;\n geo?:string;\n value?:number;\n data?:any;\n reference_id?:string;\n session_id?:string;\n}\n\nexport interface ILastReferenceIdManager {\n getLast(): string;\n clearLast(): void;\n setLast(eventId:string): void;\n}\n\nexport interface ILog {\n info(message:string):void;\n warn(message:string):void;\n error(message:string):void;\n}\n\n \n\nexport interface IEventQueue {\n enqueue(event:IEvent):void;\n process(isAppExiting?:boolean):void;\n suspendProcessing(durationInMinutes?:number, discardFutureQueuedItems?:boolean, clearQueue?:boolean):void;\n}\n\n \n\nexport interface IEnvironmentInfoCollector {\n getEnvironmentInfo(context:EventPluginContext):IEnvironmentInfo;\n}\n\n \n\nexport interface IErrorParser {\n parse(context:EventPluginContext, exception:Error): IError;\n}\n\n \n\nexport interface IModuleCollector {\n getModules(context:EventPluginContext):IModule[];\n}\n\n \n\nexport interface IRequestInfoCollector {\n getRequestInfo(context:EventPluginContext):IRequestInfo;\n}\n\n \n\nexport interface IStorage {\n save(path:string, value:T):boolean;\n get(path:string):T;\n getList(searchPattern?:string, limit?:number):IStorageItem[];\n remove(path:string):void;\n}\n\n \n\nexport interface ISubmissionAdapter {\n sendRequest(request:SubmissionRequest, callback:SubmissionCallback, isAppExiting?:boolean): void;\n}\n\n \n\nexport interface ISubmissionClient {\n postEvents(events:IEvent[], config:Configuration, callback:(response:SubmissionResponse) => void, isAppExiting?:boolean):void;\n postUserDescription(referenceId:string, description:IUserDescription, config:Configuration, callback:(response:SubmissionResponse) => void):void;\n getSettings(config:Configuration, callback:(response:SettingsResponse) => void):void;\n}\n\n \n\nexport interface IConfigurationSettings {\n apiKey?:string;\n serverUrl?:string;\n environmentInfoCollector?:IEnvironmentInfoCollector;\n errorParser?:IErrorParser;\n lastReferenceIdManager?:ILastReferenceIdManager;\n log?:ILog;\n moduleCollector?:IModuleCollector;\n requestInfoCollector?:IRequestInfoCollector;\n submissionBatchSize?:number;\n submissionClient?:ISubmissionClient;\n submissionAdapter?:ISubmissionAdapter;\n storage?:IStorage;\n queue?:IEventQueue;\n}\n\n \n\nexport class SettingsManager {\n /**\n * The configuration settings path.\n * @type {string}\n * @private\n */\n private static _configPath:string = 'ex-server-settings.json';\n\n /**\n * A list of handlers that will be fired when the settings change.\n * @type {Array}\n * @private\n */\n private static _handlers:{ (config:Configuration):void }[] = [];\n\n public static onChanged(handler:(config:Configuration) => void) {\n !!handler && this._handlers.push(handler);\n }\n\n public static applySavedServerSettings(config:Configuration):void {\n config.log.info('Applying saved settings.');\n config.settings = Utils.merge(config.settings, this.getSavedServerSettings(config));\n this.changed(config);\n }\n\n public static checkVersion(version:number, config:Configuration):void {\n if (version) {\n let savedConfigVersion = parseInt(config.storage.get(`${this._configPath}-version`), 10);\n if (isNaN(savedConfigVersion) || version > savedConfigVersion) {\n config.log.info(`Updating settings from v${(!isNaN(savedConfigVersion) ? savedConfigVersion : 0)} to v${version}`);\n this.updateSettings(config);\n }\n }\n }\n\n public static updateSettings(config:Configuration):void {\n if (!config.isValid) {\n config.log.error('Unable to update settings: ApiKey is not set.');\n return;\n }\n\n config.submissionClient.getSettings(config, (response:SettingsResponse) => {\n if (!response || !response.success || !response.settings) {\n return;\n }\n\n config.settings = Utils.merge(config.settings, response.settings);\n\n // TODO: Store snapshot of settings after reading from config and attributes and use that to revert to defaults.\n // Remove any existing server settings that are not in the new server settings.\n let savedServerSettings = SettingsManager.getSavedServerSettings(config);\n for (let key in savedServerSettings) {\n if (response.settings[key]) {\n continue;\n }\n\n delete config.settings[key];\n }\n\n let path = SettingsManager._configPath; // optimization for minifier.\n config.storage.save(`${path}-version`, response.settingsVersion);\n config.storage.save(path, response.settings);\n\n config.log.info('Updated settings');\n this.changed(config);\n });\n }\n\n private static changed(config:Configuration) {\n let handlers = this._handlers; // optimization for minifier.\n for (let index = 0; index < handlers.length; index++) {\n handlers[index](config);\n }\n }\n\n private static getSavedServerSettings(config:Configuration):Object {\n return config.storage.get(this._configPath) || {};\n }\n}\n\n \n\nexport class DefaultLastReferenceIdManager implements ILastReferenceIdManager {\n /**\n * Gets the last event's reference id that was submitted to the server.\n * @type {string}\n * @private\n */\n private _lastReferenceId:string = null;\n\n /**\n * Gets the last event's reference id that was submitted to the server.\n * @returns {string}\n */\n getLast(): string {\n return this._lastReferenceId;\n }\n\n /**\n * Clears the last event's reference id.\n */\n clearLast():void {\n this._lastReferenceId = null;\n }\n\n /**\n * Sets the last event's reference id.\n * @param eventId\n */\n setLast(eventId:string):void {\n this._lastReferenceId = eventId;\n }\n}\n\n \n\nexport class ConsoleLog implements ILog {\n public info(message:string):void {\n this.log('info', message);\n }\n\n public warn(message:string):void {\n this.log('warn', message);\n }\n\n public error(message:string):void {\n this.log('error', message);\n }\n\n private log(level:string, message:string) {\n if (console && console[level]) {\n console[level](`[${level}] Exceptionless: ${message}`);\n }\n }\n}\n\n \n\nexport class NullLog implements ILog {\n public info(message:string):void {}\n public warn(message:string):void {}\n public error(message:string):void {}\n}\n\nexport interface IUserInfo {\n identity?:string;\n name?:string;\n data?:any;\n}\n\n \n\nexport interface IEventPlugin {\n priority?:number;\n name?:string;\n run(context:EventPluginContext, next?:() => void): void;\n}\n\n \n\nexport class EventPluginContext {\n public cancelled:boolean;\n public client:ExceptionlessClient;\n public event:IEvent;\n public contextData:ContextData;\n\n constructor(client:ExceptionlessClient, event:IEvent, contextData?:ContextData) {\n this.client = client;\n this.event = event;\n this.contextData = contextData ? contextData : new ContextData();\n }\n\n public get log(): ILog {\n return this.client.config.log;\n }\n}\n\n \n\nexport class EventPluginManager {\n public static run(context:EventPluginContext, callback:(context?:EventPluginContext) => void): void {\n let wrap = function (plugin:IEventPlugin, next?:() => void): () => void {\n return () => {\n try {\n if (!context.cancelled) {\n plugin.run(context, next);\n }\n } catch (ex) {\n context.cancelled = true;\n context.log.error(`Error running plugin '${plugin.name}': ${ex.message}. Discarding Event.`);\n }\n\n if (context.cancelled && !!callback) {\n callback(context);\n }\n };\n };\n\n let plugins:IEventPlugin[] = context.client.config.plugins; // optimization for minifier.\n let wrappedPlugins:{ (): void }[] = [];\n if (!!callback) {\n wrappedPlugins[plugins.length] = wrap({ name: 'cb', priority: 9007199254740992, run: callback }, null);\n }\n\n for (let index = plugins.length - 1; index > -1; index--) {\n wrappedPlugins[index] = wrap(plugins[index], !!callback || (index < plugins.length - 1) ? wrappedPlugins[index + 1] : null);\n }\n\n wrappedPlugins[0]();\n }\n\n public static addDefaultPlugins(config:Configuration): void {\n config.addPlugin(new ConfigurationDefaultsPlugin());\n config.addPlugin(new ErrorPlugin());\n config.addPlugin(new ModuleInfoPlugin());\n config.addPlugin(new RequestInfoPlugin());\n config.addPlugin(new EnvironmentInfoPlugin());\n config.addPlugin(new SubmissionMethodPlugin());\n }\n}\n\n \n\nexport class ReferenceIdPlugin implements IEventPlugin {\n public priority:number = 20;\n public name:string = 'ReferenceIdPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n if ((!context.event.reference_id || context.event.reference_id.length === 0) && context.event.type === 'error') {\n context.event.reference_id = Utils.guid().replace('-', '').substring(0, 10);\n }\n\n next && next();\n }\n}\n\n \n\nexport class DefaultEventQueue implements IEventQueue {\n /**\n * The configuration object.\n * @type {Configuration}\n * @private\n */\n private _config:Configuration;\n\n /**\n * Suspends processing until the specified time.\n * @type {Date}\n * @private\n */\n private _suspendProcessingUntil:Date;\n\n /**\n * Discards queued items until the specified time.\n * @type {Date}\n * @private\n */\n private _discardQueuedItemsUntil:Date;\n\n /**\n * Returns true if the queue is processing.\n * @type {boolean}\n * @private\n */\n private _processingQueue:boolean = false;\n\n /**\n * Processes the queue every xx seconds.\n * @type {Timer}\n * @private\n */\n private _queueTimer:any;\n\n constructor(config:Configuration) {\n this._config = config;\n }\n\n public enqueue(event:IEvent): void {\n let config:Configuration = this._config; // Optimization for minifier.\n this.ensureQueueTimer();\n\n if (this.areQueuedItemsDiscarded()) {\n config.log.info('Queue items are currently being discarded. The event will not be queued.');\n return;\n }\n\n let key = `ex-q-${new Date().toJSON()}-${Utils.randomNumber()}`;\n config.log.info(`Enqueuing event: ${key} type=${event.type} ${!!event.reference_id ? 'refid=' + event.reference_id : ''}`);\n config.storage.save(key, event);\n }\n\n public process(isAppExiting?:boolean): void {\n function getEvents(events:{ path:string, value:IEvent }[]):IEvent[] {\n let items:IEvent[] = [];\n for (let index = 0; index < events.length; index++) {\n items.push(events[index].value);\n }\n\n return items;\n }\n\n const queueNotProcessed:string = 'The queue will not be processed.'; // optimization for minifier.\n let config:Configuration = this._config; // Optimization for minifier.\n let log:ILog = config.log; // Optimization for minifier.\n\n this.ensureQueueTimer();\n\n if (this._processingQueue) {\n return;\n }\n\n log.info('Processing queue...');\n if (!config.enabled) {\n log.info(`Configuration is disabled. ${queueNotProcessed}`);\n return;\n }\n\n if (!config.isValid) {\n log.info(`Invalid Api Key. ${queueNotProcessed}`);\n return;\n }\n\n this._processingQueue = true;\n\n try {\n let events = config.storage.getList('ex-q', config.submissionBatchSize);\n if (!events || events.length === 0) {\n this._processingQueue = false;\n return;\n }\n\n log.info(`Sending ${events.length} events to ${config.serverUrl}.`);\n config.submissionClient.postEvents(getEvents(events), config, (response:SubmissionResponse) => {\n this.processSubmissionResponse(response, events);\n log.info('Finished processing queue.');\n this._processingQueue = false;\n }, isAppExiting);\n } catch (ex) {\n log.error(`Error processing queue: ${ex}`);\n this.suspendProcessing();\n this._processingQueue = false;\n }\n }\n\n public suspendProcessing(durationInMinutes?:number, discardFutureQueuedItems?:boolean, clearQueue?:boolean): void {\n let config:Configuration = this._config; // Optimization for minifier.\n\n if (!durationInMinutes || durationInMinutes <= 0) {\n durationInMinutes = 5;\n }\n\n config.log.info(`Suspending processing for ${durationInMinutes} minutes.`);\n this._suspendProcessingUntil = new Date(new Date().getTime() + (durationInMinutes * 60000));\n\n if (discardFutureQueuedItems) {\n this._discardQueuedItemsUntil = new Date(new Date().getTime() + (durationInMinutes * 60000));\n }\n\n if (clearQueue) {\n // Account is over the limit and we want to ensure that the sample size being sent in will contain newer errors.\n this.removeEvents(config.storage.getList('ex-q'));\n }\n }\n\n private areQueuedItemsDiscarded(): boolean {\n return this._discardQueuedItemsUntil && this._discardQueuedItemsUntil > new Date();\n }\n\n private ensureQueueTimer(): void {\n if (!this._queueTimer) {\n this._queueTimer = setInterval(() => this.onProcessQueue(), 10000);\n }\n }\n\n private isQueueProcessingSuspended(): boolean {\n return this._suspendProcessingUntil && this._suspendProcessingUntil > new Date();\n }\n\n private onProcessQueue(): void {\n if (!this.isQueueProcessingSuspended() && !this._processingQueue) {\n this.process();\n }\n }\n\n private processSubmissionResponse(response:SubmissionResponse, events:{ path:string, value:IEvent }[]): void {\n const noSubmission:string = 'The event will not be submitted.'; // Optimization for minifier.\n let config:Configuration = this._config; // Optimization for minifier.\n let log:ILog = config.log; // Optimization for minifier.\n\n if (response.success) {\n log.info(`Sent ${events.length} events.`);\n this.removeEvents(events);\n return;\n }\n\n if (response.serviceUnavailable) {\n // You are currently over your rate limit or the servers are under stress.\n log.error('Server returned service unavailable.');\n this.suspendProcessing();\n return;\n }\n\n if (response.paymentRequired) {\n // If the organization over the rate limit then discard the event.\n log.info('Too many events have been submitted, please upgrade your plan.');\n this.suspendProcessing(null, true, true);\n return;\n }\n\n if (response.unableToAuthenticate) {\n // The api key was suspended or could not be authorized.\n log.info(`Unable to authenticate, please check your configuration. ${noSubmission}`);\n this.suspendProcessing(15);\n this.removeEvents(events);\n return;\n }\n\n if (response.notFound || response.badRequest) {\n // The service end point could not be found.\n log.error(`Error while trying to submit data: ${response.message}`);\n this.suspendProcessing(60 * 4);\n this.removeEvents(events);\n return;\n }\n\n if (response.requestEntityTooLarge) {\n let message = 'Event submission discarded for being too large.';\n if (config.submissionBatchSize > 1) {\n log.error(`${message} Retrying with smaller batch size.`);\n config.submissionBatchSize = Math.max(1, Math.round(config.submissionBatchSize / 1.5));\n } else {\n log.error(`${message} ${noSubmission}`);\n this.removeEvents(events);\n }\n\n return;\n }\n\n if (!response.success) {\n log.error(`Error submitting events: ${response.message || 'Please check the network tab for more info.'}`);\n this.suspendProcessing();\n }\n }\n\n private removeEvents(events:{ path:string, value:IEvent }[]) {\n for (let index = 0; index < (events || []).length; index++) {\n this._config.storage.remove(events[index].path);\n }\n }\n}\n\n \n\nexport class InMemoryStorage implements IStorage {\n private _items:IStorageItem[] = [];\n private _maxItems:number;\n\n constructor(maxItems?:number) {\n this._maxItems = maxItems > 0 ? maxItems : 250;\n }\n\n public save(path:string, value:T):boolean {\n if (!path || !value) {\n return false;\n }\n\n this.remove(path);\n if (this._items.push({ created: new Date().getTime(), path: path, value: value }) > this._maxItems) {\n this._items.shift();\n }\n\n return true;\n }\n\n public get(path:string):T {\n let item:IStorageItem = path ? this.getList(`^${path}$`, 1)[0] : null;\n return item ? item.value : null;\n }\n\n public getList(searchPattern?:string, limit?:number):IStorageItem[] {\n let items = this._items; // Optimization for minifier\n if (!searchPattern) {\n return items.slice(0, limit);\n }\n\n let regex = new RegExp(searchPattern);\n let results:IStorageItem[] = [];\n for (let index = 0; index < items.length; index++) {\n if (regex.test(items[index].path)) {\n results.push(items[index]);\n\n if (results.length >= limit) {\n break;\n }\n }\n }\n\n return results;\n }\n\n public remove(path:string):void {\n if (path) {\n let item = this.getList(`^${path}$`, 1)[0];\n if (item) {\n this._items.splice(this._items.indexOf(item), 1);\n }\n }\n }\n}\n\n \n\ndeclare var XDomainRequest:{ new (); create(); };\n\nexport class DefaultSubmissionClient implements ISubmissionClient {\n public configurationVersionHeader:string = 'x-exceptionless-configversion';\n\n public postEvents(events:IEvent[], config:Configuration, callback:(response:SubmissionResponse) => void, isAppExiting?:boolean):void {\n let data = Utils.stringify(events, config.dataExclusions);\n let request = this.createRequest(config, 'POST', '/api/v2/events', data);\n let cb = this.createSubmissionCallback(config, callback);\n\n return config.submissionAdapter.sendRequest(request, cb, isAppExiting);\n }\n\n public postUserDescription(referenceId:string, description:IUserDescription, config:Configuration, callback:(response:SubmissionResponse) => void):void {\n let path = `/api/v2/events/by-ref/${encodeURIComponent(referenceId)}/user-description`;\n let data = Utils.stringify(description, config.dataExclusions);\n let request = this.createRequest(config, 'POST', path, data);\n let cb = this.createSubmissionCallback(config, callback);\n\n return config.submissionAdapter.sendRequest(request, cb);\n }\n\n public getSettings(config:Configuration, callback:(response:SettingsResponse) => void):void {\n let request = this.createRequest(config, 'GET', '/api/v2/projects/config');\n let cb = (status, message, data?, headers?) => {\n if (status !== 200) {\n return callback(new SettingsResponse(false, null, -1, null, message));\n }\n\n let settings:IClientConfiguration;\n try {\n settings = JSON.parse(data);\n } catch (e) {\n config.log.error(`Unable to parse settings: '${data}'`);\n }\n\n if (!settings || isNaN(settings.version)) {\n return callback(new SettingsResponse(false, null, -1, null, 'Invalid configuration settings.'));\n }\n\n callback(new SettingsResponse(true, settings.settings || {}, settings.version));\n };\n\n return config.submissionAdapter.sendRequest(request, cb);\n }\n\n private createRequest(config: Configuration, method: string, path: string, data: string = null): SubmissionRequest {\n return {\n method,\n path,\n data,\n serverUrl: config.serverUrl,\n apiKey: config.apiKey,\n userAgent: config.userAgent\n };\n }\n\n private createSubmissionCallback(config:Configuration, callback:(response:SubmissionResponse) => void) {\n return (status, message, data?, headers?) => {\n let settingsVersion:number = headers && parseInt(headers[this.configurationVersionHeader], 10);\n SettingsManager.checkVersion(settingsVersion, config);\n\n callback(new SubmissionResponse(status, message));\n };\n }\n}\n\nexport class Utils {\n public static addRange(target:T[], ...values:T[]) {\n if (!target) {\n target = [];\n }\n\n if (!values || values.length === 0) {\n return target;\n }\n\n for (let index = 0; index < values.length; index++) {\n if (values[index] && target.indexOf(values[index]) < 0) {\n target.push(values[index]);\n }\n }\n\n return target;\n }\n\n public static getHashCode(source:string): string {\n if (!source || source.length === 0) {\n return null;\n }\n\n let hash:number = 0;\n for (let index = 0; index < source.length; index++) {\n let character = source.charCodeAt(index);\n hash = ((hash << 5) - hash) + character;\n hash |= 0;\n }\n\n return hash.toString();\n }\n\n public static getCookies(cookies:string): Object {\n let result:Object = {};\n\n let parts:string[] = (cookies || '').split('; ');\n for (let index = 0; index < parts.length; index++) {\n let cookie:string[] = parts[index].split('=');\n result[cookie[0]] = cookie[1];\n }\n\n return result;\n }\n\n public static guid(): string {\n function s4() {\n return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);\n }\n\n return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();\n }\n\n public static merge(defaultValues:Object, values:Object) {\n let result:Object = {};\n\n for (let key in defaultValues || {}) {\n if (!!defaultValues[key]) {\n result[key] = defaultValues[key];\n }\n }\n\n for (let key in values || {}) {\n if (!!values[key]) {\n result[key] = values[key];\n }\n }\n\n return result;\n }\n\n public static parseVersion(source:string): string {\n if (!source) {\n return null;\n }\n\n let versionRegex = /(v?((\\d+)\\.(\\d+)(\\.(\\d+))?)(?:-([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?(?:\\+([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?)/;\n let matches = versionRegex.exec(source);\n if (matches && matches.length > 0) {\n return matches[0];\n }\n\n return null;\n }\n\n public static parseQueryString(query:string) {\n if (!query || query.length === 0) {\n return null;\n }\n\n let pairs:string[] = query.split('&');\n if (pairs.length === 0) {\n return null;\n }\n\n let result:Object = {};\n for (let index = 0; index < pairs.length; index++) {\n let pair = pairs[index].split('=');\n result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);\n }\n\n return result;\n }\n\n public static randomNumber(): number {\n return Math.floor(Math.random() * 9007199254740992);\n }\n\n public static stringify(data:any, exclusions?:string[]): string {\n function checkForMatch(pattern:string, value:string): boolean {\n if (!pattern || !value || typeof value !== 'string') {\n return false;\n }\n\n let trim = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;\n pattern = pattern.toLowerCase().replace(trim, '');\n value = value.toLowerCase().replace(trim, '');\n\n if (pattern.length <= 0) {\n return false;\n }\n\n let startsWithWildcard:boolean = pattern[0] === '*';\n if (startsWithWildcard) {\n pattern = pattern.slice(1);\n }\n\n let endsWithWildcard:boolean = pattern[pattern.length - 1] === '*';\n if (endsWithWildcard) {\n pattern = pattern.substring(0, pattern.length - 1);\n }\n\n if (startsWithWildcard && endsWithWildcard) {\n return value.indexOf(pattern) !== -1;\n }\n\n if (startsWithWildcard) {\n return value.lastIndexOf(pattern) === (value.length - pattern.length);\n }\n\n if (endsWithWildcard) {\n return value.indexOf(pattern) === 0;\n }\n\n return value === pattern;\n }\n\n function stringifyImpl(obj:any, excludedKeys:string[]): string {\n let cache:string[] = [];\n return JSON.stringify(obj, function(key:string, value:any) {\n for (let index = 0; index < (excludedKeys || []).length; index++) {\n if (checkForMatch(excludedKeys[index], key)) {\n return;\n }\n }\n\n if (typeof value === 'object' && !!value) {\n if (cache.indexOf(value) !== -1) {\n // Circular reference found, discard key\n return;\n }\n\n cache.push(value);\n }\n\n return value;\n });\n }\n\n if (({}).toString.call(data) === '[object Array]') {\n let result = [];\n for (let index = 0; index < data.length; index++) {\n result[index] = JSON.parse(stringifyImpl(data[index], exclusions || []));\n }\n\n return JSON.stringify(result);\n }\n\n return stringifyImpl(data, exclusions || []);\n }\n}\n\n \n\nexport class Configuration implements IConfigurationSettings {\n /**\n * The default configuration settings that are applied to new configuration instances.\n * @type {IConfigurationSettings}\n * @private\n */\n private static _defaultSettings:IConfigurationSettings = null;\n\n /**\n * A default list of tags that will automatically be added to every\n * report submitted to the server.\n *\n * @type {Array}\n */\n public defaultTags:string[] = [];\n\n /**\n * A default list of of extended data objects that will automatically\n * be added to every report submitted to the server.\n *\n * @type {{}}\n */\n public defaultData:Object = {};\n\n /**\n * Whether the client is currently enabled or not. If it is disabled,\n * submitted errors will be discarded and no data will be sent to the server.\n *\n * @returns {boolean}\n */\n public enabled:boolean = true;\n\n public environmentInfoCollector:IEnvironmentInfoCollector;\n public errorParser:IErrorParser;\n public lastReferenceIdManager:ILastReferenceIdManager = new DefaultLastReferenceIdManager();\n public log:ILog;\n public moduleCollector:IModuleCollector;\n public requestInfoCollector:IRequestInfoCollector;\n\n /**\n * Maximum number of events that should be sent to the server together in a batch. (Defaults to 50)\n */\n public submissionBatchSize:number;\n public submissionAdapter:ISubmissionAdapter;\n public submissionClient:ISubmissionClient;\n\n /**\n * Contains a dictionary of custom settings that can be used to control\n * the client and will be automatically updated from the server.\n */\n public settings:Object = {};\n\n public storage:IStorage;\n\n public queue:IEventQueue;\n\n /**\n * The list of plugins that will be used in this configuration.\n * @type {Array}\n * @private\n */\n private _plugins:IEventPlugin[] = [];\n\n constructor(configSettings?:IConfigurationSettings) {\n function inject(fn:any) {\n return typeof fn === 'function' ? fn(this) : fn;\n }\n\n configSettings = Utils.merge(Configuration.defaults, configSettings);\n\n this.log = inject(configSettings.log) || new NullLog();\n this.apiKey = configSettings.apiKey;\n this.serverUrl = configSettings.serverUrl;\n\n this.environmentInfoCollector = inject(configSettings.environmentInfoCollector);\n this.errorParser = inject(configSettings.errorParser);\n this.lastReferenceIdManager = inject(configSettings.lastReferenceIdManager) || new DefaultLastReferenceIdManager();\n this.moduleCollector = inject(configSettings.moduleCollector);\n this.requestInfoCollector = inject(configSettings.requestInfoCollector);\n this.submissionBatchSize = inject(configSettings.submissionBatchSize) || 50;\n this.submissionAdapter = inject(configSettings.submissionAdapter);\n this.submissionClient = inject(configSettings.submissionClient) || new DefaultSubmissionClient();\n this.storage = inject(configSettings.storage) || new InMemoryStorage();\n this.queue = inject(configSettings.queue) || new DefaultEventQueue(this);\n\n SettingsManager.applySavedServerSettings(this);\n EventPluginManager.addDefaultPlugins(this);\n }\n\n /**\n * The API key that will be used when sending events to the server.\n * @type {string}\n * @private\n */\n private _apiKey:string;\n\n /**\n * The API key that will be used when sending events to the server.\n * @returns {string}\n */\n public get apiKey():string {\n return this._apiKey;\n }\n\n /**\n * The API key that will be used when sending events to the server.\n * @param value\n */\n public set apiKey(value:string) {\n this._apiKey = value || null;\n this.log.info(`apiKey: ${this._apiKey}`);\n }\n\n /**\n * Returns true if the apiKey is valid.\n * @returns {boolean}\n */\n public get isValid():boolean {\n return !!this.apiKey && this.apiKey.length >= 10;\n }\n\n /**\n * The server url that all events will be sent to.\n * @type {string}\n * @private\n */\n private _serverUrl:string = 'https://collector.exceptionless.io';\n\n /**\n * The server url that all events will be sent to.\n * @returns {string}\n */\n public get serverUrl():string {\n return this._serverUrl;\n }\n\n /**\n * The server url that all events will be sent to.\n * @param value\n */\n public set serverUrl(value:string) {\n if (!!value) {\n this._serverUrl = value;\n this.log.info(`serverUrl: ${this._serverUrl}`);\n }\n }\n\n /**\n * A list of exclusion patterns.\n * @type {Array}\n * @private\n */\n private _dataExclusions:string[] = [];\n\n /**\n * A list of exclusion patterns that will automatically remove any data that\n * matches them from any data submitted to the server.\n *\n * For example, entering CreditCard will remove any extended data properties,\n * form fields, cookies and query parameters from the report.\n *\n * @returns {string[]}\n */\n public get dataExclusions():string[] {\n let exclusions:string = this.settings['@@DataExclusions'];\n return this._dataExclusions.concat(exclusions && exclusions.split(',') || []);\n }\n\n /**\n * Add items to the list of exclusion patterns that will automatically remove any\n * data that matches them from any data submitted to the server.\n *\n * For example, entering CreditCard will remove any extended data properties, form\n * fields, cookies and query parameters from the report.\n *\n * @param exclusions\n */\n public addDataExclusions(...exclusions:string[]) {\n this._dataExclusions = Utils.addRange(this._dataExclusions, ...exclusions);\n }\n\n /**\n * The list of plugins that will be used in this configuration.\n * @returns {IEventPlugin[]}\n */\n public get plugins():IEventPlugin[] {\n return this._plugins.sort((p1:IEventPlugin, p2:IEventPlugin) => {\n return (p1.priority < p2.priority) ? -1 : (p1.priority > p2.priority) ? 1 : 0;\n });\n }\n\n /**\n * Register an plugin to be used in this configuration.\n * @param plugin\n */\n public addPlugin(plugin:IEventPlugin): void;\n\n /**\n * Register an plugin to be used in this configuration.\n * @param name The name used to identify the plugin.\n * @param priority Used to determine plugins priority.\n * @param pluginAction A function that is run.\n */\n public addPlugin(name:string, priority:number, pluginAction:(context:EventPluginContext, next?:() => void) => void): void;\n public addPlugin(pluginOrName:IEventPlugin|string, priority?:number, pluginAction?:(context:EventPluginContext, next?:() => void) => void): void {\n let plugin:IEventPlugin = !!pluginAction ? { name: pluginOrName, priority: priority, run: pluginAction } : pluginOrName;\n if (!plugin || !plugin.run) {\n this.log.error('Add plugin failed: Run method not defined');\n return;\n }\n\n if (!plugin.name) {\n plugin.name = Utils.guid();\n }\n\n if (!plugin.priority) {\n plugin.priority = 0;\n }\n\n let pluginExists:boolean = false;\n let plugins = this._plugins; // optimization for minifier.\n for (let index = 0; index < plugins.length; index++) {\n if (plugins[index].name === plugin.name) {\n pluginExists = true;\n break;\n }\n }\n\n if (!pluginExists) {\n plugins.push(plugin);\n }\n }\n\n /**\n * Remove the plugin from this configuration.\n * @param plugin\n */\n public removePlugin(plugin:IEventPlugin): void;\n\n /**\n * Remove an plugin by key from this configuration.\n * @param name\n */\n public removePlugin(name:string): void;\n public removePlugin(pluginOrName:IEventPlugin|string): void {\n let name:string = typeof pluginOrName === 'string' ? pluginOrName : pluginOrName.name;\n if (!name) {\n this.log.error('Remove plugin failed: Plugin name not defined');\n return;\n }\n\n let plugins = this._plugins; // optimization for minifier.\n for (let index = 0; index < plugins.length; index++) {\n if (plugins[index].name === name) {\n plugins.splice(index, 1);\n break;\n }\n }\n }\n\n /**\n * Automatically set the application version for events.\n * @param version\n */\n public setVersion(version:string): void {\n if (!!version) {\n this.defaultData['@version'] = version;\n }\n }\n\n public setUserIdentity(userInfo:IUserInfo): void;\n public setUserIdentity(identity:string): void;\n public setUserIdentity(identity:string, name:string): void;\n public setUserIdentity(userInfoOrIdentity:IUserInfo|string, name?:string): void {\n const USER_KEY:string = '@user'; // optimization for minifier.\n let userInfo:IUserInfo = typeof userInfoOrIdentity !== 'string' ? userInfoOrIdentity : { identity: userInfoOrIdentity, name: name };\n\n let shouldRemove:boolean = !userInfo || (!userInfo.identity && !userInfo.name);\n if (shouldRemove) {\n delete this.defaultData[USER_KEY];\n } else {\n this.defaultData[USER_KEY] = userInfo;\n }\n\n this.log.info(`user identity: ${shouldRemove ? 'null' : userInfo.identity}`);\n }\n\n /**\n * Used to identify the client that sent the events to the server.\n * @returns {string}\n */\n public get userAgent():string {\n return 'exceptionless-js/1.0.0.0';\n }\n\n /**\n * Automatically set a reference id for error events.\n */\n public useReferenceIds(): void {\n this.addPlugin(new ReferenceIdPlugin());\n }\n\n // TODO: Support a min log level.\n public useDebugLogger(): void {\n this.log = new ConsoleLog();\n }\n\n /**\n * The default configuration settings that are applied to new configuration instances.\n * @returns {IConfigurationSettings}\n */\n public static get defaults() {\n if (Configuration._defaultSettings === null) {\n Configuration._defaultSettings = {};\n }\n\n return Configuration._defaultSettings;\n }\n}\n\n \n\nexport class EventBuilder {\n public target:IEvent;\n public client:ExceptionlessClient;\n public pluginContextData:ContextData;\n\n private _validIdentifierErrorMessage:string = 'must contain between 8 and 100 alphanumeric or \\'-\\' characters.'; // optimization for minifier.\n\n constructor(event:IEvent, client:ExceptionlessClient, pluginContextData?:ContextData) {\n this.target = event;\n this.client = client;\n this.pluginContextData = pluginContextData || new ContextData();\n }\n\n public setType(type:string): EventBuilder {\n if (!!type) {\n this.target.type = type;\n }\n\n return this;\n }\n\n public setSource(source:string): EventBuilder {\n if (!!source) {\n this.target.source = source;\n }\n\n return this;\n }\n\n public setSessionId(sessionId:string): EventBuilder {\n if (!this.isValidIdentifier(sessionId)) {\n throw new Error(`SessionId ${this._validIdentifierErrorMessage}`);\n }\n\n this.target.session_id = sessionId;\n return this;\n }\n\n public setReferenceId(referenceId:string): EventBuilder {\n if (!this.isValidIdentifier(referenceId)) {\n throw new Error(`ReferenceId ${this._validIdentifierErrorMessage}`);\n }\n\n this.target.reference_id = referenceId;\n return this;\n }\n\n public setMessage(message:string): EventBuilder {\n if (!!message) {\n this.target.message = message;\n }\n\n return this;\n }\n\n public setGeo(latitude: number, longitude: number): EventBuilder {\n if (latitude < -90.0 || latitude > 90.0) {\n throw new Error('Must be a valid latitude value between -90.0 and 90.0.');\n }\n\n if (longitude < -180.0 || longitude > 180.0) {\n throw new Error('Must be a valid longitude value between -180.0 and 180.0.');\n }\n\n this.target.geo = `${latitude},${longitude}`;\n return this;\n }\n\n public setUserIdentity(userInfo:IUserInfo): EventBuilder;\n public setUserIdentity(identity:string): EventBuilder;\n public setUserIdentity(identity:string, name:string): EventBuilder;\n public setUserIdentity(userInfoOrIdentity:IUserInfo|string, name?:string): EventBuilder {\n let userInfo = typeof userInfoOrIdentity !== 'string' ? userInfoOrIdentity : { identity: userInfoOrIdentity, name: name };\n if (!userInfo || (!userInfo.identity && !userInfo.name)) {\n return this;\n }\n\n this.setProperty('@user', userInfo);\n return this;\n }\n\n public setValue(value:number): EventBuilder {\n if (!!value) {\n this.target.value = value;\n }\n\n return this;\n }\n\n public addTags(...tags:string[]): EventBuilder {\n this.target.tags = Utils.addRange(this.target.tags, ...tags);\n return this;\n }\n\n public setProperty(name:string, value:any): EventBuilder {\n if (!name || (value === undefined || value == null)) {\n return this;\n }\n\n if (!this.target.data) {\n this.target.data = {};\n }\n\n this.target.data[name] = value;\n return this;\n }\n\n public markAsCritical(critical:boolean): EventBuilder {\n if (critical) {\n this.addTags('Critical');\n }\n\n return this;\n }\n\n public addRequestInfo(request:Object): EventBuilder {\n if (!!request) {\n this.pluginContextData['@request'] = request;\n }\n\n return this;\n }\n\n public submit(callback?:(context:EventPluginContext) => void): void {\n this.client.submitEvent(this.target, this.pluginContextData, callback);\n }\n\n private isValidIdentifier(value:string): boolean {\n if (!value) {\n return true;\n }\n\n if (value.length < 8 || value.length > 100) {\n return false;\n }\n\n for (var index = 0; index < value.length; index++) {\n let code = value.charCodeAt(index);\n let isDigit = (code >= 48) && (code <= 57);\n let isLetter = ((code >= 65) && (code <= 90)) || ((code >= 97) && (code <= 122));\n let isMinus = code === 45;\n\n if (!(isDigit || isLetter) && !isMinus) {\n return false;\n }\n }\n\n return true;\n }\n}\n\nexport interface IUserDescription {\n email_address?:string;\n description?:string;\n data?:any;\n}\n\nexport class ContextData {\n public setException(exception:Error): void {\n if (exception) {\n this['@@_Exception'] = exception;\n }\n }\n\n public get hasException(): boolean {\n return !!this['@@_Exception'];\n }\n\n public getException(): Error {\n return this['@@_Exception'] || null;\n }\n\n public markAsUnhandledError(): void {\n this['@@_IsUnhandledError'] = true;\n }\n\n public get isUnhandledError(): boolean {\n return !!this['@@_IsUnhandledError'];\n }\n\n public setSubmissionMethod(method:string): void {\n if (method) {\n this['@@_SubmissionMethod'] = method;\n }\n }\n\n public getSubmissionMethod(): string {\n return this['@@_SubmissionMethod'] || null;\n }\n}\n\nexport class SubmissionResponse {\n success:boolean = false;\n badRequest:boolean = false;\n serviceUnavailable:boolean = false;\n paymentRequired:boolean = false;\n unableToAuthenticate:boolean = false;\n notFound:boolean = false;\n requestEntityTooLarge:boolean = false;\n statusCode:number;\n message:string;\n\n constructor(statusCode:number, message?:string) {\n this.statusCode = statusCode;\n this.message = message;\n\n this.success = statusCode >= 200 && statusCode <= 299;\n this.badRequest = statusCode === 400;\n this.serviceUnavailable = statusCode === 503;\n this.paymentRequired = statusCode === 402;\n this.unableToAuthenticate = statusCode === 401 || statusCode === 403;\n this.notFound = statusCode === 404;\n this.requestEntityTooLarge = statusCode === 413;\n }\n}\n\n \n\nexport class ExceptionlessClient {\n /**\n * The default ExceptionlessClient instance.\n * @type {ExceptionlessClient}\n * @private\n */\n private static _instance:ExceptionlessClient = null;\n\n public config:Configuration;\n\n constructor();\n constructor(settings:IConfigurationSettings);\n constructor(apiKey:string, serverUrl?:string);\n constructor(settingsOrApiKey?:IConfigurationSettings|string, serverUrl?:string) {\n if (typeof settingsOrApiKey !== 'object') {\n this.config = new Configuration(settingsOrApiKey);\n } else {\n this.config = new Configuration({ apiKey: settingsOrApiKey, serverUrl: serverUrl });\n }\n }\n\n public createException(exception:Error): EventBuilder {\n let pluginContextData = new ContextData();\n pluginContextData.setException(exception);\n return this.createEvent(pluginContextData).setType('error');\n }\n\n public submitException(exception:Error, callback?:(context:EventPluginContext) => void): void {\n this.createException(exception).submit(callback);\n }\n\n public createUnhandledException(exception:Error, submissionMethod?:string): EventBuilder {\n let builder = this.createException(exception);\n builder.pluginContextData.markAsUnhandledError();\n builder.pluginContextData.setSubmissionMethod(submissionMethod);\n\n return builder;\n }\n\n public submitUnhandledException(exception:Error, submissionMethod?:string, callback?:(context:EventPluginContext) => void) {\n this.createUnhandledException(exception, submissionMethod).submit(callback);\n }\n\n public createFeatureUsage(feature:string): EventBuilder {\n return this.createEvent().setType('usage').setSource(feature);\n }\n\n public submitFeatureUsage(feature:string, callback?:(context:EventPluginContext) => void): void {\n this.createFeatureUsage(feature).submit(callback);\n }\n\n public createLog(message:string): EventBuilder;\n public createLog(source:string, message:string): EventBuilder;\n public createLog(source:string, message:string, level:string): EventBuilder;\n public createLog(sourceOrMessage:string, message?:string, level?:string): EventBuilder {\n let builder = this.createEvent().setType('log');\n\n if (message && level) {\n builder = builder.setSource(sourceOrMessage).setMessage(message).setProperty('@level', level);\n } else if (message) {\n builder = builder.setSource(sourceOrMessage).setMessage(message);\n } else {\n // TODO: Look into using https://www.stevefenton.co.uk/Content/Blog/Date/201304/Blog/Obtaining-A-Class-Name-At-Runtime-In-TypeScript/\n let caller:any = arguments.callee.caller;\n builder = builder.setSource(caller && caller.name).setMessage(sourceOrMessage);\n }\n\n return builder;\n }\n\n public submitLog(message:string): void;\n public submitLog(source:string, message:string): void;\n public submitLog(source:string, message:string, level:string, callback?:(context:EventPluginContext) => void): void;\n public submitLog(sourceOrMessage:string, message?:string, level?:string, callback?:(context:EventPluginContext) => void): void {\n this.createLog(sourceOrMessage, message, level).submit(callback);\n }\n\n public createNotFound(resource:string): EventBuilder {\n return this.createEvent().setType('404').setSource(resource);\n }\n\n public submitNotFound(resource:string, callback?:(context:EventPluginContext) => void): void {\n this.createNotFound(resource).submit(callback);\n }\n\n public createSessionStart(sessionId:string): EventBuilder {\n return this.createEvent().setType('start').setSessionId(sessionId);\n }\n\n public submitSessionStart(sessionId:string, callback?:(context:EventPluginContext) => void): void {\n this.createSessionStart(sessionId).submit(callback);\n }\n\n public createSessionEnd(sessionId:string): EventBuilder {\n return this.createEvent().setType('end').setSessionId(sessionId);\n }\n\n public submitSessionEnd(sessionId:string, callback?:(context:EventPluginContext) => void): void {\n this.createSessionEnd(sessionId).submit(callback);\n }\n\n public createEvent(pluginContextData?:ContextData): EventBuilder {\n return new EventBuilder({ date: new Date() }, this, pluginContextData);\n }\n\n /**\n * Submits the event to be sent to the server.\n * @param event The event data.\n * @param pluginContextData Any contextual data objects to be used by Exceptionless plugins to gather default information for inclusion in the report information.\n * @param callback\n */\n public submitEvent(event:IEvent, pluginContextData?:ContextData, callback?:(context:EventPluginContext) => void): void {\n function cancelled(context:EventPluginContext) {\n if (!!context) {\n context.cancelled = true;\n }\n\n return !!callback && callback(context);\n }\n\n let context = new EventPluginContext(this, event, pluginContextData);\n if (!event) {\n return cancelled(context);\n }\n\n if (!this.config.enabled) {\n this.config.log.info('Event submission is currently disabled.');\n return cancelled(context);\n }\n\n if (!event.data) {\n event.data = {};\n }\n\n if (!event.tags || !event.tags.length) {\n event.tags = [];\n }\n\n EventPluginManager.run(context, function (ctx:EventPluginContext) {\n let ev = ctx.event;\n if (!ctx.cancelled) {\n // ensure all required data\n if (!ev.type || ev.type.length === 0) {\n ev.type = 'log';\n }\n\n if (!ev.date) {\n ev.date = new Date();\n }\n\n let config = ctx.client.config;\n config.queue.enqueue(ev);\n\n if (ev.reference_id && ev.reference_id.length > 0) {\n ctx.log.info(`Setting last reference id '${ev.reference_id}'`);\n config.lastReferenceIdManager.setLast(ev.reference_id);\n }\n }\n\n !!callback && callback(ctx);\n });\n }\n\n /**\n * Updates the user's email address and description of an event for the specified reference id.\n * @param referenceId The reference id of the event to update.\n * @param email The user's email address to set on the event.\n * @param description The user's description of the event.\n */\n public updateUserEmailAndDescription(referenceId:string, email:string, description:string, callback?:(response:SubmissionResponse) => void) {\n if (!referenceId || !email || !description || !this.config.enabled) {\n return !!callback && callback(new SubmissionResponse(500, 'cancelled'));\n }\n\n let userDescription:IUserDescription = { email_address: email, description: description };\n this.config.submissionClient.postUserDescription(referenceId, userDescription, this.config, (response:SubmissionResponse) => {\n if (!response.success) {\n this.config.log.error(`Failed to submit user email and description for event '${referenceId}': ${response.statusCode} ${response.message}`);\n }\n\n !!callback && callback(response);\n });\n }\n\n /**\n * Gets the last event client id that was submitted to the server.\n * @returns {string} The event client id.\n */\n public getLastReferenceId(): string {\n return this.config.lastReferenceIdManager.getLast();\n }\n\n /**\n * The default ExceptionlessClient instance.\n * @type {ExceptionlessClient}\n */\n public static get default() {\n if (ExceptionlessClient._instance === null) {\n ExceptionlessClient._instance = new ExceptionlessClient(null);\n }\n\n return ExceptionlessClient._instance;\n }\n}\n\nexport interface IModule {\n data?:any;\n\n module_id?:number;\n name?:string;\n version?:string;\n is_entry?:boolean;\n created_date?:Date;\n modified_date?:Date;\n}\n\nexport interface IRequestInfo {\n user_agent?:string;\n http_method?:string;\n is_secure?:boolean;\n host?:string;\n port?:number;\n path?:string;\n referrer?:string;\n client_ip_address?:string;\n cookies?:any;\n post_data?:any;\n query_string?:any;\n data?:any;\n}\n\nexport interface IEnvironmentInfo {\n processor_count?:number;\n total_physical_memory?:number;\n available_physical_memory?:number;\n command_line?:string;\n process_name?:string;\n process_id?:string;\n process_memory_size?:number;\n thread_id?:string;\n architecture?:string;\n o_s_name?:string;\n o_s_version?:string;\n ip_address?:string;\n machine_name?:string;\n install_id?:string;\n runtime_version?:string;\n data?:any;\n}\n\n \n\nexport class ConfigurationDefaultsPlugin implements IEventPlugin {\n public priority:number = 10;\n public name:string = 'ConfigurationDefaultsPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n let defaultTags:string[] = context.client.config.defaultTags || [];\n for (let index = 0; index < defaultTags.length; index++) {\n let tag = defaultTags[index];\n if (!!tag && context.event.tags.indexOf(tag) < 0) {\n context.event.tags.push(tag);\n }\n }\n\n let defaultData:Object = context.client.config.defaultData || {};\n for (let key in defaultData) {\n if (!!defaultData[key]) {\n context.event.data[key] = defaultData[key];\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class ErrorPlugin implements IEventPlugin {\n public priority:number = 30;\n public name:string = 'ErrorPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ERROR_KEY:string = '@error'; // optimization for minifier.\n\n let exception = context.contextData.getException();\n if (!!exception) {\n context.event.type = 'error';\n\n if (!context.event.data[ERROR_KEY]) {\n let parser = context.client.config.errorParser;\n if (!parser) {\n throw new Error('No error parser was defined.');\n }\n\n let result = parser.parse(context, exception);\n if (!!result) {\n context.event.data[ERROR_KEY] = result;\n }\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class ModuleInfoPlugin implements IEventPlugin {\n public priority:number = 40;\n public name:string = 'ModuleInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ERROR_KEY:string = '@error'; // optimization for minifier.\n\n let collector = context.client.config.moduleCollector;\n if (context.event.data[ERROR_KEY] && !context.event.data['@error'].modules && !!collector) {\n let modules:IModule[] = collector.getModules(context);\n if (modules && modules.length > 0) {\n context.event.data[ERROR_KEY].modules = modules;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class RequestInfoPlugin implements IEventPlugin {\n public priority:number = 60;\n public name:string = 'RequestInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const REQUEST_KEY:string = '@request'; // optimization for minifier.\n\n let collector = context.client.config.requestInfoCollector;\n if (!context.event.data[REQUEST_KEY] && !!collector) {\n let requestInfo:IRequestInfo = collector.getRequestInfo(context);\n if (!!requestInfo) {\n context.event.data[REQUEST_KEY] = requestInfo;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class EnvironmentInfoPlugin implements IEventPlugin {\n public priority:number = 70;\n public name:string = 'EnvironmentInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ENVIRONMENT_KEY:string = '@environment'; // optimization for minifier.\n\n let collector = context.client.config.environmentInfoCollector;\n if (!context.event.data[ENVIRONMENT_KEY] && collector) {\n let environmentInfo:IEnvironmentInfo = collector.getEnvironmentInfo(context);\n if (!!environmentInfo) {\n context.event.data[ENVIRONMENT_KEY] = environmentInfo;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class SubmissionMethodPlugin implements IEventPlugin {\n public priority:number = 100;\n public name:string = 'SubmissionMethodPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n let submissionMethod:string = context.contextData.getSubmissionMethod();\n if (!!submissionMethod) {\n context.event.data['@submission_method'] = submissionMethod;\n }\n\n next && next();\n }\n}\n\nexport interface IParameter {\n data?:any;\n generic_arguments?:string[];\n\n name?:string;\n type?:string;\n type_namespace?:string;\n}\n\n \n\nexport interface IMethod {\n data?:any;\n generic_arguments?:string[];\n parameters?:IParameter[];\n\n is_signature_target?:boolean;\n declaring_namespace?:string;\n declaring_type?:string;\n name?:string;\n module_id?:number;\n}\n\n \n\nexport interface IStackFrame extends IMethod {\n file_name?:string;\n line_number?:number;\n column?:number;\n}\n\n \n\nexport interface IInnerError {\n message?:string;\n type?:string;\n code?:string;\n data?:any;\n inner?:IInnerError;\n stack_trace?:IStackFrame[];\n target_method?:IMethod;\n}\n\n \n\nexport interface IError extends IInnerError {\n modules?:IModule[];\n}\n\nexport interface IStorageItem {\n created:number;\n path:string;\n value:T;\n}\n\nexport interface SubmissionCallback {\n (status: number, message: string, data?: string, headers?: Object): void;\n}\n\nexport interface SubmissionRequest {\n serverUrl: string;\n apiKey: string;\n userAgent: string;\n method: string;\n path: string;\n data: string;\n}\n\nexport class SettingsResponse {\n success:boolean = false;\n settings:any;\n settingsVersion:number = -1;\n message:string;\n exception:any;\n\n constructor(success:boolean, settings:any, settingsVersion:number = -1, exception:any = null, message:string = null) {\n this.success = success;\n this.settings = settings;\n this.settingsVersion = settingsVersion;\n this.exception = exception;\n this.message = message;\n }\n}\n\nexport interface IClientConfiguration {\n settings:Object;\n version:number;\n}\n\nimport os = require('os');\nimport nodestacktrace = require('stack-trace');\nimport path = require('path');\nimport https = require('https');\nimport url = require('url');\n \n\nexport class NodeEnvironmentInfoCollector implements IEnvironmentInfoCollector {\n public getEnvironmentInfo(context:EventPluginContext): IEnvironmentInfo {\n function getIpAddresses():string {\n let ips:string[] = [];\n let interfaces = os.networkInterfaces();\n Object.keys(interfaces).forEach((name) => {\n interfaces[name].forEach((iface:any) => {\n if ('IPv4' === iface.family && !iface.internal) {\n ips.push(iface.address);\n }\n });\n });\n\n return ips.join(', ');\n }\n\n if (!os) {\n return null;\n }\n\n let environmentInfo: IEnvironmentInfo = {\n processor_count: os.cpus().length,\n total_physical_memory: os.totalmem(),\n available_physical_memory: os.freemem(),\n command_line: process.argv.join(' '),\n process_name: (process.title || '').replace(/[\\uE000-\\uF8FF]/g, ''),\n process_id: process.pid + '',\n process_memory_size: process.memoryUsage().heapTotal,\n // thread_id: '',\n architecture: os.arch(),\n o_s_name: os.type(),\n o_s_version: os.release(),\n ip_address: getIpAddresses(),\n machine_name: os.hostname(),\n // install_id: '',\n runtime_version: process.version,\n data: {\n loadavg: os.loadavg(),\n platform: os.platform(),\n tmpdir: os.tmpdir(),\n uptime: os.uptime()\n }\n };\n\n if ((os).endianness) {\n environmentInfo.data.endianness = (os).endianness();\n }\n\n return environmentInfo;\n }\n}\n\n \n\nexport class NodeErrorParser implements IErrorParser {\n public parse(context:EventPluginContext, exception:Error): IError {\n function getStackFrames(stackFrames:any[]): IStackFrame[] {\n let frames:IStackFrame[] = [];\n\n for (let index = 0; index < stackFrames.length; index++) {\n let frame = stackFrames[index];\n frames.push({\n name: frame.getMethodName() || frame.getFunctionName(),\n // parameters: frame.args,\n file_name: frame.getFileName(),\n line_number: frame.getLineNumber() || 0,\n column: frame.getColumnNumber() || 0,\n declaring_type: frame.getTypeName(),\n data: {\n is_native: frame.isNative() || (!!frame.filename && frame.filename[0] !== '/' && frame.filename[0] !== '.')\n }\n });\n }\n\n return frames;\n }\n\n if (!nodestacktrace) {\n throw new Error('Unable to load the stack trace library.');\n }\n\n let stackFrames = nodestacktrace.parse(exception) || [];\n return {\n type: exception.name,\n message: exception.message,\n stack_trace: getStackFrames(stackFrames)\n };\n }\n}\n\n \n\nexport class NodeModuleCollector implements IModuleCollector {\n\n private initialized:boolean = false;\n private installedModules:{[id:string]: IModule} = {};\n\n public getModules(context:EventPluginContext): IModule[] {\n this.initialize();\n\n if (!require.main) {\n return [];\n }\n\n let modulePath = path.dirname(require.main.filename) + '/node_modules/';\n let pathLength = modulePath.length;\n\n let loadedKeys = Object.keys(require.cache);\n let loadedModules = {};\n\n loadedKeys.forEach(key => {\n let id = key.substr(pathLength);\n console.log(id);\n id = id.substr(0, id.indexOf('/'));\n loadedModules[id] = true;\n });\n\n return Object.keys(loadedModules)\n .map(key => this.installedModules[key])\n .filter(m => m !== undefined);\n }\n\n private initialize() {\n if (this.initialized) {\n return;\n }\n\n this.initialized = true;\n\n let output = child.spawnSync('npm', ['ls', '--depth=0', '--json']).stdout;\n\n if (!output) {\n return;\n }\n\n let json;\n try {\n json = JSON.parse(output.toString());\n } catch (e) {\n return;\n }\n\n let items = json.dependencies;\n if (!items) {\n return;\n }\n\n let id = 0;\n this.installedModules = {};\n\n Object.keys(items).forEach(key => {\n let item = items[key];\n let theModule = {\n module_id: id++,\n name: key,\n version: item.version\n };\n\n this.installedModules[key] = theModule;\n });\n }\n}\n\n \n\nexport class NodeRequestInfoCollector implements IRequestInfoCollector {\n getRequestInfo(context:EventPluginContext):IRequestInfo {\n const REQUEST_KEY:string = '@request'; // optimization for minifier.\n if (!context.contextData[REQUEST_KEY]) {\n return null;\n }\n\n let request = context.contextData[REQUEST_KEY];\n // TODO: include referrer\n let requestInfo:IRequestInfo = {\n client_ip_address: request.ip,\n user_agent: request.headers['user-agent'],\n is_secure: request.secure,\n http_method: request.method,\n host: request.hostname || request.host,\n path: request.path,\n post_data: request.body,\n cookies: Utils.getCookies((request || {}).headers.cookie),\n query_string: request.params\n };\n\n let host = request.headers.host;\n let port:number = host && parseInt(host.slice(host.indexOf(':') + 1), 10);\n if (port > 0) {\n requestInfo.port = port;\n }\n\n return requestInfo;\n }\n}\n\n \n\nexport class NodeSubmissionAdapter implements ISubmissionAdapter {\n public sendRequest(request:SubmissionRequest, callback:SubmissionCallback, isAppExiting?:boolean) {\n if (isAppExiting) {\n this.sendRequestSync(request, callback);\n return;\n }\n\n let parsedHost = url.parse(request.serverUrl);\n\n let options: https.RequestOptions = {\n auth: `client:${request.apiKey}`,\n headers: {},\n hostname: parsedHost.hostname,\n method: request.method,\n port: parsedHost.port && parseInt(parsedHost.port, 10),\n path: request.path\n };\n\n options.headers['User-Agent'] = request.userAgent;\n\n if (request.method === 'POST') {\n options.headers = {\n 'Content-Type': 'application/json',\n 'Content-Length': request.data.length\n };\n }\n\n let protocol = (parsedHost.protocol === 'https' ? https : http);\n let clientRequest: http.ClientRequest = protocol.request(options, (response: http.IncomingMessage) => {\n let body = '';\n response.setEncoding('utf8');\n response.on('data', (chunk) => body += chunk);\n response.on('end', () => this.complete(response, body, response.headers, callback));\n });\n\n clientRequest.on('error', (error: Error) => callback(500, error.message));\n clientRequest.end(request.data);\n }\n\n private complete(response: http.IncomingMessage, responseBody: string, responseHeaders: Object, callback: SubmissionCallback): void {\n let message: string;\n if (response.statusCode === 0) {\n message = 'Unable to connect to server.';\n } else if (response.statusCode < 200 || response.statusCode > 299) {\n message = response.statusMessage || (response).message;\n }\n\n callback(response.statusCode || 500, message, responseBody, responseHeaders);\n }\n\n private sendRequestSync(request: SubmissionRequest, callback: SubmissionCallback): void {\n let requestJson = JSON.stringify(request);\n let res = child.spawnSync(process.execPath, [require.resolve('./submitSync.js')],\n {\n input: requestJson,\n stdio: ['pipe', 'pipe', process.stderr]\n });\n\n let out = res.stdout.toString();\n let result = JSON.parse(out);\n\n callback(result.status, result.message, result.data, result.headers);\n }\n}\n\n \n\nconst EXIT: string = 'exit';\nconst UNCAUGHT_EXCEPTION: string = 'uncaughtException';\nconst SIGINT: string = 'SIGINT';\nconst SIGINT_CODE: number = 2;\n\nlet defaults = Configuration.defaults;\ndefaults.environmentInfoCollector = new NodeEnvironmentInfoCollector();\ndefaults.errorParser = new NodeErrorParser();\ndefaults.moduleCollector = new NodeModuleCollector();\ndefaults.requestInfoCollector = new NodeRequestInfoCollector();\ndefaults.submissionAdapter = new NodeSubmissionAdapter();\n\nfunction getListenerCount(emitter, event:string): number {\n if (emitter.listenerCount) {\n return emitter.listenerCount(event);\n }\n return require('events').listenerCount(emitter, event);\n}\n\n/*\n * Adding a event handler for 'uncaughtException' modifies the default\n * Node behavior, so it won't exit or log to the console. Instead,\n * we hijack the event emitter and forward the exception to the callback.\n */\nfunction onUncaughtException(callback: (error: Error) => void) {\n let originalEmit = process.emit;\n\n process.emit = function(type: string, error: Error) {\n if (type === UNCAUGHT_EXCEPTION) {\n callback(error);\n }\n\n return originalEmit.apply(this, arguments);\n };\n}\n\nonUncaughtException(function(error: Error) {\n ExceptionlessClient.default.submitUnhandledException(error, UNCAUGHT_EXCEPTION);\n});\n\n/*\n * We cannot hijack SIGINT, so if there are no other handlers,\n * we just reproduce default Node.js behavior by exiting.\n */\nprocess.on(SIGINT, function() {\n if (getListenerCount(process, SIGINT) <= 1) {\n process.exit(128 + SIGINT_CODE);\n }\n});\n\nprocess.on(EXIT, function(code: number) {\n /**\n * exit codes: https://nodejs.org/api/process.html#process_event_exit\n * From now on, only synchronous code may run. As soon as this method\n * ends, the application inevitably will exit.\n */\n function getExitCodeReason(exitCode: number): string {\n if (exitCode === 1) {\n return 'Uncaught Fatal Exception';\n }\n\n if (exitCode === 3) {\n return 'Internal JavaScript Parse Error';\n }\n\n if (exitCode === 4) {\n return 'Internal JavaScript Evaluation Failure';\n }\n\n if (exitCode === 5) {\n return 'Fatal Exception';\n }\n\n if (exitCode === 6) {\n return 'Non-function Internal Exception Handler ';\n }\n\n if (exitCode === 7) {\n return 'Internal Exception Handler Run-Time Failure';\n }\n\n if (exitCode === 8) {\n return 'Uncaught Exception';\n }\n\n if (exitCode === 9) {\n return 'Invalid Argument';\n }\n\n if (exitCode === 10) {\n return 'Internal JavaScript Run-Time Failure';\n }\n\n if (exitCode === 12) {\n return 'Invalid Debug Argument';\n }\n\n return null;\n }\n\n let client = ExceptionlessClient.default;\n let message = getExitCodeReason(code);\n\n if (message !== null) {\n client.submitLog(EXIT, message, 'Error');\n }\n\n client.config.queue.process(true);\n // Application will now exit.\n});\n\n(Error).stackTraceLimit = Infinity;\n\n"]} \ No newline at end of file diff --git a/src/services/NodeModuleCollector.ts b/src/services/NodeModuleCollector.ts index 45a40d12..53b7c867 100644 --- a/src/services/NodeModuleCollector.ts +++ b/src/services/NodeModuleCollector.ts @@ -14,16 +14,18 @@ export class NodeModuleCollector implements IModuleCollector { public getModules(context:EventPluginContext): IModule[] { this.initialize(); - if (!require.main) return []; + if (!require.main) { + return []; + } - var modulePath = path.dirname(require.main.filename) + '/node_modules/'; - var pathLength = modulePath.length; + let modulePath = path.dirname(require.main.filename) + '/node_modules/'; + let pathLength = modulePath.length; - var loadedKeys = Object.keys(require.cache); - var loadedModules = {}; + let loadedKeys = Object.keys(require.cache); + let loadedModules = {}; loadedKeys.forEach(key => { - var id = key.substr(pathLength); + let id = key.substr(pathLength); console.log(id); id = id.substr(0, id.indexOf('/')); loadedModules[id] = true; @@ -35,28 +37,36 @@ export class NodeModuleCollector implements IModuleCollector { } private initialize() { - if (this.initialized) return; + if (this.initialized) { + return; + } + this.initialized = true; - var output = child.spawnSync('npm', ['ls', '--depth=0', '--json']).stdout; + let output = child.spawnSync('npm', ['ls', '--depth=0', '--json']).stdout; - if (!output) return; + if (!output) { + return; + } - var json; + let json; try { json = JSON.parse(output.toString()); + } catch (e) { + return; } - catch (e) { return; } - var items = json.dependencies; - if (!items) return; + let items = json.dependencies; + if (!items) { + return; + } - var id = 0; + let id = 0; this.installedModules = {}; Object.keys(items).forEach(key => { - var item = items[key]; - var theModule = { + let item = items[key]; + let theModule = { module_id: id++, name: key, version: item.version From 56944dbc5499817cf40e855ebc61781356d1ae89 Mon Sep 17 00:00:00 2001 From: Frank Ebersoll Date: Tue, 20 Oct 2015 23:37:29 +0200 Subject: [PATCH 4/7] Updated tslint to capture console.log --- dist/exceptionless.node.js | 1 - dist/exceptionless.node.js.map | 2 +- src/services/NodeModuleCollector.ts | 1 - tslint.json | 3 ++- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/dist/exceptionless.node.js b/dist/exceptionless.node.js index 87ecbf68..cbae732e 100644 --- a/dist/exceptionless.node.js +++ b/dist/exceptionless.node.js @@ -1306,7 +1306,6 @@ var NodeModuleCollector = (function () { var loadedModules = {}; loadedKeys.forEach(function (key) { var id = key.substr(pathLength); - console.log(id); id = id.substr(0, id.indexOf('/')); loadedModules[id] = true; }); diff --git a/dist/exceptionless.node.js.map b/dist/exceptionless.node.js.map index dbc51741..6557ed10 100644 --- a/dist/exceptionless.node.js.map +++ b/dist/exceptionless.node.js.map @@ -1 +1 @@ -{"version":3,"file":"exceptionless.node.js","sourceRoot":"/source/","sources":["exceptionless.node.ts"],"names":["getListenerCount","onUncaughtException","getExitCodeReason"],"mappings":"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","sourcesContent":["import * as events from \"events\";\nimport * as net from \"net\";\nimport * as stream from \"stream\";\nimport * as child from \"child_process\";\nimport * as tls from \"tls\";\nimport * as http from \"http\";\nimport * as crypto from \"crypto\";\nexport interface IEvent {\n type?:string;\n source?:string;\n date?:Date;\n tags?:string[];\n message?:string;\n geo?:string;\n value?:number;\n data?:any;\n reference_id?:string;\n session_id?:string;\n}\n\nexport interface ILastReferenceIdManager {\n getLast(): string;\n clearLast(): void;\n setLast(eventId:string): void;\n}\n\nexport interface ILog {\n info(message:string):void;\n warn(message:string):void;\n error(message:string):void;\n}\n\n \n\nexport interface IEventQueue {\n enqueue(event:IEvent):void;\n process(isAppExiting?:boolean):void;\n suspendProcessing(durationInMinutes?:number, discardFutureQueuedItems?:boolean, clearQueue?:boolean):void;\n}\n\n \n\nexport interface IEnvironmentInfoCollector {\n getEnvironmentInfo(context:EventPluginContext):IEnvironmentInfo;\n}\n\n \n\nexport interface IErrorParser {\n parse(context:EventPluginContext, exception:Error): IError;\n}\n\n \n\nexport interface IModuleCollector {\n getModules(context:EventPluginContext):IModule[];\n}\n\n \n\nexport interface IRequestInfoCollector {\n getRequestInfo(context:EventPluginContext):IRequestInfo;\n}\n\n \n\nexport interface IStorage {\n save(path:string, value:T):boolean;\n get(path:string):T;\n getList(searchPattern?:string, limit?:number):IStorageItem[];\n remove(path:string):void;\n}\n\n \n\nexport interface ISubmissionAdapter {\n sendRequest(request:SubmissionRequest, callback:SubmissionCallback, isAppExiting?:boolean): void;\n}\n\n \n\nexport interface ISubmissionClient {\n postEvents(events:IEvent[], config:Configuration, callback:(response:SubmissionResponse) => void, isAppExiting?:boolean):void;\n postUserDescription(referenceId:string, description:IUserDescription, config:Configuration, callback:(response:SubmissionResponse) => void):void;\n getSettings(config:Configuration, callback:(response:SettingsResponse) => void):void;\n}\n\n \n\nexport interface IConfigurationSettings {\n apiKey?:string;\n serverUrl?:string;\n environmentInfoCollector?:IEnvironmentInfoCollector;\n errorParser?:IErrorParser;\n lastReferenceIdManager?:ILastReferenceIdManager;\n log?:ILog;\n moduleCollector?:IModuleCollector;\n requestInfoCollector?:IRequestInfoCollector;\n submissionBatchSize?:number;\n submissionClient?:ISubmissionClient;\n submissionAdapter?:ISubmissionAdapter;\n storage?:IStorage;\n queue?:IEventQueue;\n}\n\n \n\nexport class SettingsManager {\n /**\n * The configuration settings path.\n * @type {string}\n * @private\n */\n private static _configPath:string = 'ex-server-settings.json';\n\n /**\n * A list of handlers that will be fired when the settings change.\n * @type {Array}\n * @private\n */\n private static _handlers:{ (config:Configuration):void }[] = [];\n\n public static onChanged(handler:(config:Configuration) => void) {\n !!handler && this._handlers.push(handler);\n }\n\n public static applySavedServerSettings(config:Configuration):void {\n config.log.info('Applying saved settings.');\n config.settings = Utils.merge(config.settings, this.getSavedServerSettings(config));\n this.changed(config);\n }\n\n public static checkVersion(version:number, config:Configuration):void {\n if (version) {\n let savedConfigVersion = parseInt(config.storage.get(`${this._configPath}-version`), 10);\n if (isNaN(savedConfigVersion) || version > savedConfigVersion) {\n config.log.info(`Updating settings from v${(!isNaN(savedConfigVersion) ? savedConfigVersion : 0)} to v${version}`);\n this.updateSettings(config);\n }\n }\n }\n\n public static updateSettings(config:Configuration):void {\n if (!config.isValid) {\n config.log.error('Unable to update settings: ApiKey is not set.');\n return;\n }\n\n config.submissionClient.getSettings(config, (response:SettingsResponse) => {\n if (!response || !response.success || !response.settings) {\n return;\n }\n\n config.settings = Utils.merge(config.settings, response.settings);\n\n // TODO: Store snapshot of settings after reading from config and attributes and use that to revert to defaults.\n // Remove any existing server settings that are not in the new server settings.\n let savedServerSettings = SettingsManager.getSavedServerSettings(config);\n for (let key in savedServerSettings) {\n if (response.settings[key]) {\n continue;\n }\n\n delete config.settings[key];\n }\n\n let path = SettingsManager._configPath; // optimization for minifier.\n config.storage.save(`${path}-version`, response.settingsVersion);\n config.storage.save(path, response.settings);\n\n config.log.info('Updated settings');\n this.changed(config);\n });\n }\n\n private static changed(config:Configuration) {\n let handlers = this._handlers; // optimization for minifier.\n for (let index = 0; index < handlers.length; index++) {\n handlers[index](config);\n }\n }\n\n private static getSavedServerSettings(config:Configuration):Object {\n return config.storage.get(this._configPath) || {};\n }\n}\n\n \n\nexport class DefaultLastReferenceIdManager implements ILastReferenceIdManager {\n /**\n * Gets the last event's reference id that was submitted to the server.\n * @type {string}\n * @private\n */\n private _lastReferenceId:string = null;\n\n /**\n * Gets the last event's reference id that was submitted to the server.\n * @returns {string}\n */\n getLast(): string {\n return this._lastReferenceId;\n }\n\n /**\n * Clears the last event's reference id.\n */\n clearLast():void {\n this._lastReferenceId = null;\n }\n\n /**\n * Sets the last event's reference id.\n * @param eventId\n */\n setLast(eventId:string):void {\n this._lastReferenceId = eventId;\n }\n}\n\n \n\nexport class ConsoleLog implements ILog {\n public info(message:string):void {\n this.log('info', message);\n }\n\n public warn(message:string):void {\n this.log('warn', message);\n }\n\n public error(message:string):void {\n this.log('error', message);\n }\n\n private log(level:string, message:string) {\n if (console && console[level]) {\n console[level](`[${level}] Exceptionless: ${message}`);\n }\n }\n}\n\n \n\nexport class NullLog implements ILog {\n public info(message:string):void {}\n public warn(message:string):void {}\n public error(message:string):void {}\n}\n\nexport interface IUserInfo {\n identity?:string;\n name?:string;\n data?:any;\n}\n\n \n\nexport interface IEventPlugin {\n priority?:number;\n name?:string;\n run(context:EventPluginContext, next?:() => void): void;\n}\n\n \n\nexport class EventPluginContext {\n public cancelled:boolean;\n public client:ExceptionlessClient;\n public event:IEvent;\n public contextData:ContextData;\n\n constructor(client:ExceptionlessClient, event:IEvent, contextData?:ContextData) {\n this.client = client;\n this.event = event;\n this.contextData = contextData ? contextData : new ContextData();\n }\n\n public get log(): ILog {\n return this.client.config.log;\n }\n}\n\n \n\nexport class EventPluginManager {\n public static run(context:EventPluginContext, callback:(context?:EventPluginContext) => void): void {\n let wrap = function (plugin:IEventPlugin, next?:() => void): () => void {\n return () => {\n try {\n if (!context.cancelled) {\n plugin.run(context, next);\n }\n } catch (ex) {\n context.cancelled = true;\n context.log.error(`Error running plugin '${plugin.name}': ${ex.message}. Discarding Event.`);\n }\n\n if (context.cancelled && !!callback) {\n callback(context);\n }\n };\n };\n\n let plugins:IEventPlugin[] = context.client.config.plugins; // optimization for minifier.\n let wrappedPlugins:{ (): void }[] = [];\n if (!!callback) {\n wrappedPlugins[plugins.length] = wrap({ name: 'cb', priority: 9007199254740992, run: callback }, null);\n }\n\n for (let index = plugins.length - 1; index > -1; index--) {\n wrappedPlugins[index] = wrap(plugins[index], !!callback || (index < plugins.length - 1) ? wrappedPlugins[index + 1] : null);\n }\n\n wrappedPlugins[0]();\n }\n\n public static addDefaultPlugins(config:Configuration): void {\n config.addPlugin(new ConfigurationDefaultsPlugin());\n config.addPlugin(new ErrorPlugin());\n config.addPlugin(new ModuleInfoPlugin());\n config.addPlugin(new RequestInfoPlugin());\n config.addPlugin(new EnvironmentInfoPlugin());\n config.addPlugin(new SubmissionMethodPlugin());\n }\n}\n\n \n\nexport class ReferenceIdPlugin implements IEventPlugin {\n public priority:number = 20;\n public name:string = 'ReferenceIdPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n if ((!context.event.reference_id || context.event.reference_id.length === 0) && context.event.type === 'error') {\n context.event.reference_id = Utils.guid().replace('-', '').substring(0, 10);\n }\n\n next && next();\n }\n}\n\n \n\nexport class DefaultEventQueue implements IEventQueue {\n /**\n * The configuration object.\n * @type {Configuration}\n * @private\n */\n private _config:Configuration;\n\n /**\n * Suspends processing until the specified time.\n * @type {Date}\n * @private\n */\n private _suspendProcessingUntil:Date;\n\n /**\n * Discards queued items until the specified time.\n * @type {Date}\n * @private\n */\n private _discardQueuedItemsUntil:Date;\n\n /**\n * Returns true if the queue is processing.\n * @type {boolean}\n * @private\n */\n private _processingQueue:boolean = false;\n\n /**\n * Processes the queue every xx seconds.\n * @type {Timer}\n * @private\n */\n private _queueTimer:any;\n\n constructor(config:Configuration) {\n this._config = config;\n }\n\n public enqueue(event:IEvent): void {\n let config:Configuration = this._config; // Optimization for minifier.\n this.ensureQueueTimer();\n\n if (this.areQueuedItemsDiscarded()) {\n config.log.info('Queue items are currently being discarded. The event will not be queued.');\n return;\n }\n\n let key = `ex-q-${new Date().toJSON()}-${Utils.randomNumber()}`;\n config.log.info(`Enqueuing event: ${key} type=${event.type} ${!!event.reference_id ? 'refid=' + event.reference_id : ''}`);\n config.storage.save(key, event);\n }\n\n public process(isAppExiting?:boolean): void {\n function getEvents(events:{ path:string, value:IEvent }[]):IEvent[] {\n let items:IEvent[] = [];\n for (let index = 0; index < events.length; index++) {\n items.push(events[index].value);\n }\n\n return items;\n }\n\n const queueNotProcessed:string = 'The queue will not be processed.'; // optimization for minifier.\n let config:Configuration = this._config; // Optimization for minifier.\n let log:ILog = config.log; // Optimization for minifier.\n\n this.ensureQueueTimer();\n\n if (this._processingQueue) {\n return;\n }\n\n log.info('Processing queue...');\n if (!config.enabled) {\n log.info(`Configuration is disabled. ${queueNotProcessed}`);\n return;\n }\n\n if (!config.isValid) {\n log.info(`Invalid Api Key. ${queueNotProcessed}`);\n return;\n }\n\n this._processingQueue = true;\n\n try {\n let events = config.storage.getList('ex-q', config.submissionBatchSize);\n if (!events || events.length === 0) {\n this._processingQueue = false;\n return;\n }\n\n log.info(`Sending ${events.length} events to ${config.serverUrl}.`);\n config.submissionClient.postEvents(getEvents(events), config, (response:SubmissionResponse) => {\n this.processSubmissionResponse(response, events);\n log.info('Finished processing queue.');\n this._processingQueue = false;\n }, isAppExiting);\n } catch (ex) {\n log.error(`Error processing queue: ${ex}`);\n this.suspendProcessing();\n this._processingQueue = false;\n }\n }\n\n public suspendProcessing(durationInMinutes?:number, discardFutureQueuedItems?:boolean, clearQueue?:boolean): void {\n let config:Configuration = this._config; // Optimization for minifier.\n\n if (!durationInMinutes || durationInMinutes <= 0) {\n durationInMinutes = 5;\n }\n\n config.log.info(`Suspending processing for ${durationInMinutes} minutes.`);\n this._suspendProcessingUntil = new Date(new Date().getTime() + (durationInMinutes * 60000));\n\n if (discardFutureQueuedItems) {\n this._discardQueuedItemsUntil = new Date(new Date().getTime() + (durationInMinutes * 60000));\n }\n\n if (clearQueue) {\n // Account is over the limit and we want to ensure that the sample size being sent in will contain newer errors.\n this.removeEvents(config.storage.getList('ex-q'));\n }\n }\n\n private areQueuedItemsDiscarded(): boolean {\n return this._discardQueuedItemsUntil && this._discardQueuedItemsUntil > new Date();\n }\n\n private ensureQueueTimer(): void {\n if (!this._queueTimer) {\n this._queueTimer = setInterval(() => this.onProcessQueue(), 10000);\n }\n }\n\n private isQueueProcessingSuspended(): boolean {\n return this._suspendProcessingUntil && this._suspendProcessingUntil > new Date();\n }\n\n private onProcessQueue(): void {\n if (!this.isQueueProcessingSuspended() && !this._processingQueue) {\n this.process();\n }\n }\n\n private processSubmissionResponse(response:SubmissionResponse, events:{ path:string, value:IEvent }[]): void {\n const noSubmission:string = 'The event will not be submitted.'; // Optimization for minifier.\n let config:Configuration = this._config; // Optimization for minifier.\n let log:ILog = config.log; // Optimization for minifier.\n\n if (response.success) {\n log.info(`Sent ${events.length} events.`);\n this.removeEvents(events);\n return;\n }\n\n if (response.serviceUnavailable) {\n // You are currently over your rate limit or the servers are under stress.\n log.error('Server returned service unavailable.');\n this.suspendProcessing();\n return;\n }\n\n if (response.paymentRequired) {\n // If the organization over the rate limit then discard the event.\n log.info('Too many events have been submitted, please upgrade your plan.');\n this.suspendProcessing(null, true, true);\n return;\n }\n\n if (response.unableToAuthenticate) {\n // The api key was suspended or could not be authorized.\n log.info(`Unable to authenticate, please check your configuration. ${noSubmission}`);\n this.suspendProcessing(15);\n this.removeEvents(events);\n return;\n }\n\n if (response.notFound || response.badRequest) {\n // The service end point could not be found.\n log.error(`Error while trying to submit data: ${response.message}`);\n this.suspendProcessing(60 * 4);\n this.removeEvents(events);\n return;\n }\n\n if (response.requestEntityTooLarge) {\n let message = 'Event submission discarded for being too large.';\n if (config.submissionBatchSize > 1) {\n log.error(`${message} Retrying with smaller batch size.`);\n config.submissionBatchSize = Math.max(1, Math.round(config.submissionBatchSize / 1.5));\n } else {\n log.error(`${message} ${noSubmission}`);\n this.removeEvents(events);\n }\n\n return;\n }\n\n if (!response.success) {\n log.error(`Error submitting events: ${response.message || 'Please check the network tab for more info.'}`);\n this.suspendProcessing();\n }\n }\n\n private removeEvents(events:{ path:string, value:IEvent }[]) {\n for (let index = 0; index < (events || []).length; index++) {\n this._config.storage.remove(events[index].path);\n }\n }\n}\n\n \n\nexport class InMemoryStorage implements IStorage {\n private _items:IStorageItem[] = [];\n private _maxItems:number;\n\n constructor(maxItems?:number) {\n this._maxItems = maxItems > 0 ? maxItems : 250;\n }\n\n public save(path:string, value:T):boolean {\n if (!path || !value) {\n return false;\n }\n\n this.remove(path);\n if (this._items.push({ created: new Date().getTime(), path: path, value: value }) > this._maxItems) {\n this._items.shift();\n }\n\n return true;\n }\n\n public get(path:string):T {\n let item:IStorageItem = path ? this.getList(`^${path}$`, 1)[0] : null;\n return item ? item.value : null;\n }\n\n public getList(searchPattern?:string, limit?:number):IStorageItem[] {\n let items = this._items; // Optimization for minifier\n if (!searchPattern) {\n return items.slice(0, limit);\n }\n\n let regex = new RegExp(searchPattern);\n let results:IStorageItem[] = [];\n for (let index = 0; index < items.length; index++) {\n if (regex.test(items[index].path)) {\n results.push(items[index]);\n\n if (results.length >= limit) {\n break;\n }\n }\n }\n\n return results;\n }\n\n public remove(path:string):void {\n if (path) {\n let item = this.getList(`^${path}$`, 1)[0];\n if (item) {\n this._items.splice(this._items.indexOf(item), 1);\n }\n }\n }\n}\n\n \n\ndeclare var XDomainRequest:{ new (); create(); };\n\nexport class DefaultSubmissionClient implements ISubmissionClient {\n public configurationVersionHeader:string = 'x-exceptionless-configversion';\n\n public postEvents(events:IEvent[], config:Configuration, callback:(response:SubmissionResponse) => void, isAppExiting?:boolean):void {\n let data = Utils.stringify(events, config.dataExclusions);\n let request = this.createRequest(config, 'POST', '/api/v2/events', data);\n let cb = this.createSubmissionCallback(config, callback);\n\n return config.submissionAdapter.sendRequest(request, cb, isAppExiting);\n }\n\n public postUserDescription(referenceId:string, description:IUserDescription, config:Configuration, callback:(response:SubmissionResponse) => void):void {\n let path = `/api/v2/events/by-ref/${encodeURIComponent(referenceId)}/user-description`;\n let data = Utils.stringify(description, config.dataExclusions);\n let request = this.createRequest(config, 'POST', path, data);\n let cb = this.createSubmissionCallback(config, callback);\n\n return config.submissionAdapter.sendRequest(request, cb);\n }\n\n public getSettings(config:Configuration, callback:(response:SettingsResponse) => void):void {\n let request = this.createRequest(config, 'GET', '/api/v2/projects/config');\n let cb = (status, message, data?, headers?) => {\n if (status !== 200) {\n return callback(new SettingsResponse(false, null, -1, null, message));\n }\n\n let settings:IClientConfiguration;\n try {\n settings = JSON.parse(data);\n } catch (e) {\n config.log.error(`Unable to parse settings: '${data}'`);\n }\n\n if (!settings || isNaN(settings.version)) {\n return callback(new SettingsResponse(false, null, -1, null, 'Invalid configuration settings.'));\n }\n\n callback(new SettingsResponse(true, settings.settings || {}, settings.version));\n };\n\n return config.submissionAdapter.sendRequest(request, cb);\n }\n\n private createRequest(config: Configuration, method: string, path: string, data: string = null): SubmissionRequest {\n return {\n method,\n path,\n data,\n serverUrl: config.serverUrl,\n apiKey: config.apiKey,\n userAgent: config.userAgent\n };\n }\n\n private createSubmissionCallback(config:Configuration, callback:(response:SubmissionResponse) => void) {\n return (status, message, data?, headers?) => {\n let settingsVersion:number = headers && parseInt(headers[this.configurationVersionHeader], 10);\n SettingsManager.checkVersion(settingsVersion, config);\n\n callback(new SubmissionResponse(status, message));\n };\n }\n}\n\nexport class Utils {\n public static addRange(target:T[], ...values:T[]) {\n if (!target) {\n target = [];\n }\n\n if (!values || values.length === 0) {\n return target;\n }\n\n for (let index = 0; index < values.length; index++) {\n if (values[index] && target.indexOf(values[index]) < 0) {\n target.push(values[index]);\n }\n }\n\n return target;\n }\n\n public static getHashCode(source:string): string {\n if (!source || source.length === 0) {\n return null;\n }\n\n let hash:number = 0;\n for (let index = 0; index < source.length; index++) {\n let character = source.charCodeAt(index);\n hash = ((hash << 5) - hash) + character;\n hash |= 0;\n }\n\n return hash.toString();\n }\n\n public static getCookies(cookies:string): Object {\n let result:Object = {};\n\n let parts:string[] = (cookies || '').split('; ');\n for (let index = 0; index < parts.length; index++) {\n let cookie:string[] = parts[index].split('=');\n result[cookie[0]] = cookie[1];\n }\n\n return result;\n }\n\n public static guid(): string {\n function s4() {\n return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);\n }\n\n return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();\n }\n\n public static merge(defaultValues:Object, values:Object) {\n let result:Object = {};\n\n for (let key in defaultValues || {}) {\n if (!!defaultValues[key]) {\n result[key] = defaultValues[key];\n }\n }\n\n for (let key in values || {}) {\n if (!!values[key]) {\n result[key] = values[key];\n }\n }\n\n return result;\n }\n\n public static parseVersion(source:string): string {\n if (!source) {\n return null;\n }\n\n let versionRegex = /(v?((\\d+)\\.(\\d+)(\\.(\\d+))?)(?:-([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?(?:\\+([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?)/;\n let matches = versionRegex.exec(source);\n if (matches && matches.length > 0) {\n return matches[0];\n }\n\n return null;\n }\n\n public static parseQueryString(query:string) {\n if (!query || query.length === 0) {\n return null;\n }\n\n let pairs:string[] = query.split('&');\n if (pairs.length === 0) {\n return null;\n }\n\n let result:Object = {};\n for (let index = 0; index < pairs.length; index++) {\n let pair = pairs[index].split('=');\n result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);\n }\n\n return result;\n }\n\n public static randomNumber(): number {\n return Math.floor(Math.random() * 9007199254740992);\n }\n\n public static stringify(data:any, exclusions?:string[]): string {\n function checkForMatch(pattern:string, value:string): boolean {\n if (!pattern || !value || typeof value !== 'string') {\n return false;\n }\n\n let trim = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;\n pattern = pattern.toLowerCase().replace(trim, '');\n value = value.toLowerCase().replace(trim, '');\n\n if (pattern.length <= 0) {\n return false;\n }\n\n let startsWithWildcard:boolean = pattern[0] === '*';\n if (startsWithWildcard) {\n pattern = pattern.slice(1);\n }\n\n let endsWithWildcard:boolean = pattern[pattern.length - 1] === '*';\n if (endsWithWildcard) {\n pattern = pattern.substring(0, pattern.length - 1);\n }\n\n if (startsWithWildcard && endsWithWildcard) {\n return value.indexOf(pattern) !== -1;\n }\n\n if (startsWithWildcard) {\n return value.lastIndexOf(pattern) === (value.length - pattern.length);\n }\n\n if (endsWithWildcard) {\n return value.indexOf(pattern) === 0;\n }\n\n return value === pattern;\n }\n\n function stringifyImpl(obj:any, excludedKeys:string[]): string {\n let cache:string[] = [];\n return JSON.stringify(obj, function(key:string, value:any) {\n for (let index = 0; index < (excludedKeys || []).length; index++) {\n if (checkForMatch(excludedKeys[index], key)) {\n return;\n }\n }\n\n if (typeof value === 'object' && !!value) {\n if (cache.indexOf(value) !== -1) {\n // Circular reference found, discard key\n return;\n }\n\n cache.push(value);\n }\n\n return value;\n });\n }\n\n if (({}).toString.call(data) === '[object Array]') {\n let result = [];\n for (let index = 0; index < data.length; index++) {\n result[index] = JSON.parse(stringifyImpl(data[index], exclusions || []));\n }\n\n return JSON.stringify(result);\n }\n\n return stringifyImpl(data, exclusions || []);\n }\n}\n\n \n\nexport class Configuration implements IConfigurationSettings {\n /**\n * The default configuration settings that are applied to new configuration instances.\n * @type {IConfigurationSettings}\n * @private\n */\n private static _defaultSettings:IConfigurationSettings = null;\n\n /**\n * A default list of tags that will automatically be added to every\n * report submitted to the server.\n *\n * @type {Array}\n */\n public defaultTags:string[] = [];\n\n /**\n * A default list of of extended data objects that will automatically\n * be added to every report submitted to the server.\n *\n * @type {{}}\n */\n public defaultData:Object = {};\n\n /**\n * Whether the client is currently enabled or not. If it is disabled,\n * submitted errors will be discarded and no data will be sent to the server.\n *\n * @returns {boolean}\n */\n public enabled:boolean = true;\n\n public environmentInfoCollector:IEnvironmentInfoCollector;\n public errorParser:IErrorParser;\n public lastReferenceIdManager:ILastReferenceIdManager = new DefaultLastReferenceIdManager();\n public log:ILog;\n public moduleCollector:IModuleCollector;\n public requestInfoCollector:IRequestInfoCollector;\n\n /**\n * Maximum number of events that should be sent to the server together in a batch. (Defaults to 50)\n */\n public submissionBatchSize:number;\n public submissionAdapter:ISubmissionAdapter;\n public submissionClient:ISubmissionClient;\n\n /**\n * Contains a dictionary of custom settings that can be used to control\n * the client and will be automatically updated from the server.\n */\n public settings:Object = {};\n\n public storage:IStorage;\n\n public queue:IEventQueue;\n\n /**\n * The list of plugins that will be used in this configuration.\n * @type {Array}\n * @private\n */\n private _plugins:IEventPlugin[] = [];\n\n constructor(configSettings?:IConfigurationSettings) {\n function inject(fn:any) {\n return typeof fn === 'function' ? fn(this) : fn;\n }\n\n configSettings = Utils.merge(Configuration.defaults, configSettings);\n\n this.log = inject(configSettings.log) || new NullLog();\n this.apiKey = configSettings.apiKey;\n this.serverUrl = configSettings.serverUrl;\n\n this.environmentInfoCollector = inject(configSettings.environmentInfoCollector);\n this.errorParser = inject(configSettings.errorParser);\n this.lastReferenceIdManager = inject(configSettings.lastReferenceIdManager) || new DefaultLastReferenceIdManager();\n this.moduleCollector = inject(configSettings.moduleCollector);\n this.requestInfoCollector = inject(configSettings.requestInfoCollector);\n this.submissionBatchSize = inject(configSettings.submissionBatchSize) || 50;\n this.submissionAdapter = inject(configSettings.submissionAdapter);\n this.submissionClient = inject(configSettings.submissionClient) || new DefaultSubmissionClient();\n this.storage = inject(configSettings.storage) || new InMemoryStorage();\n this.queue = inject(configSettings.queue) || new DefaultEventQueue(this);\n\n SettingsManager.applySavedServerSettings(this);\n EventPluginManager.addDefaultPlugins(this);\n }\n\n /**\n * The API key that will be used when sending events to the server.\n * @type {string}\n * @private\n */\n private _apiKey:string;\n\n /**\n * The API key that will be used when sending events to the server.\n * @returns {string}\n */\n public get apiKey():string {\n return this._apiKey;\n }\n\n /**\n * The API key that will be used when sending events to the server.\n * @param value\n */\n public set apiKey(value:string) {\n this._apiKey = value || null;\n this.log.info(`apiKey: ${this._apiKey}`);\n }\n\n /**\n * Returns true if the apiKey is valid.\n * @returns {boolean}\n */\n public get isValid():boolean {\n return !!this.apiKey && this.apiKey.length >= 10;\n }\n\n /**\n * The server url that all events will be sent to.\n * @type {string}\n * @private\n */\n private _serverUrl:string = 'https://collector.exceptionless.io';\n\n /**\n * The server url that all events will be sent to.\n * @returns {string}\n */\n public get serverUrl():string {\n return this._serverUrl;\n }\n\n /**\n * The server url that all events will be sent to.\n * @param value\n */\n public set serverUrl(value:string) {\n if (!!value) {\n this._serverUrl = value;\n this.log.info(`serverUrl: ${this._serverUrl}`);\n }\n }\n\n /**\n * A list of exclusion patterns.\n * @type {Array}\n * @private\n */\n private _dataExclusions:string[] = [];\n\n /**\n * A list of exclusion patterns that will automatically remove any data that\n * matches them from any data submitted to the server.\n *\n * For example, entering CreditCard will remove any extended data properties,\n * form fields, cookies and query parameters from the report.\n *\n * @returns {string[]}\n */\n public get dataExclusions():string[] {\n let exclusions:string = this.settings['@@DataExclusions'];\n return this._dataExclusions.concat(exclusions && exclusions.split(',') || []);\n }\n\n /**\n * Add items to the list of exclusion patterns that will automatically remove any\n * data that matches them from any data submitted to the server.\n *\n * For example, entering CreditCard will remove any extended data properties, form\n * fields, cookies and query parameters from the report.\n *\n * @param exclusions\n */\n public addDataExclusions(...exclusions:string[]) {\n this._dataExclusions = Utils.addRange(this._dataExclusions, ...exclusions);\n }\n\n /**\n * The list of plugins that will be used in this configuration.\n * @returns {IEventPlugin[]}\n */\n public get plugins():IEventPlugin[] {\n return this._plugins.sort((p1:IEventPlugin, p2:IEventPlugin) => {\n return (p1.priority < p2.priority) ? -1 : (p1.priority > p2.priority) ? 1 : 0;\n });\n }\n\n /**\n * Register an plugin to be used in this configuration.\n * @param plugin\n */\n public addPlugin(plugin:IEventPlugin): void;\n\n /**\n * Register an plugin to be used in this configuration.\n * @param name The name used to identify the plugin.\n * @param priority Used to determine plugins priority.\n * @param pluginAction A function that is run.\n */\n public addPlugin(name:string, priority:number, pluginAction:(context:EventPluginContext, next?:() => void) => void): void;\n public addPlugin(pluginOrName:IEventPlugin|string, priority?:number, pluginAction?:(context:EventPluginContext, next?:() => void) => void): void {\n let plugin:IEventPlugin = !!pluginAction ? { name: pluginOrName, priority: priority, run: pluginAction } : pluginOrName;\n if (!plugin || !plugin.run) {\n this.log.error('Add plugin failed: Run method not defined');\n return;\n }\n\n if (!plugin.name) {\n plugin.name = Utils.guid();\n }\n\n if (!plugin.priority) {\n plugin.priority = 0;\n }\n\n let pluginExists:boolean = false;\n let plugins = this._plugins; // optimization for minifier.\n for (let index = 0; index < plugins.length; index++) {\n if (plugins[index].name === plugin.name) {\n pluginExists = true;\n break;\n }\n }\n\n if (!pluginExists) {\n plugins.push(plugin);\n }\n }\n\n /**\n * Remove the plugin from this configuration.\n * @param plugin\n */\n public removePlugin(plugin:IEventPlugin): void;\n\n /**\n * Remove an plugin by key from this configuration.\n * @param name\n */\n public removePlugin(name:string): void;\n public removePlugin(pluginOrName:IEventPlugin|string): void {\n let name:string = typeof pluginOrName === 'string' ? pluginOrName : pluginOrName.name;\n if (!name) {\n this.log.error('Remove plugin failed: Plugin name not defined');\n return;\n }\n\n let plugins = this._plugins; // optimization for minifier.\n for (let index = 0; index < plugins.length; index++) {\n if (plugins[index].name === name) {\n plugins.splice(index, 1);\n break;\n }\n }\n }\n\n /**\n * Automatically set the application version for events.\n * @param version\n */\n public setVersion(version:string): void {\n if (!!version) {\n this.defaultData['@version'] = version;\n }\n }\n\n public setUserIdentity(userInfo:IUserInfo): void;\n public setUserIdentity(identity:string): void;\n public setUserIdentity(identity:string, name:string): void;\n public setUserIdentity(userInfoOrIdentity:IUserInfo|string, name?:string): void {\n const USER_KEY:string = '@user'; // optimization for minifier.\n let userInfo:IUserInfo = typeof userInfoOrIdentity !== 'string' ? userInfoOrIdentity : { identity: userInfoOrIdentity, name: name };\n\n let shouldRemove:boolean = !userInfo || (!userInfo.identity && !userInfo.name);\n if (shouldRemove) {\n delete this.defaultData[USER_KEY];\n } else {\n this.defaultData[USER_KEY] = userInfo;\n }\n\n this.log.info(`user identity: ${shouldRemove ? 'null' : userInfo.identity}`);\n }\n\n /**\n * Used to identify the client that sent the events to the server.\n * @returns {string}\n */\n public get userAgent():string {\n return 'exceptionless-js/1.0.0.0';\n }\n\n /**\n * Automatically set a reference id for error events.\n */\n public useReferenceIds(): void {\n this.addPlugin(new ReferenceIdPlugin());\n }\n\n // TODO: Support a min log level.\n public useDebugLogger(): void {\n this.log = new ConsoleLog();\n }\n\n /**\n * The default configuration settings that are applied to new configuration instances.\n * @returns {IConfigurationSettings}\n */\n public static get defaults() {\n if (Configuration._defaultSettings === null) {\n Configuration._defaultSettings = {};\n }\n\n return Configuration._defaultSettings;\n }\n}\n\n \n\nexport class EventBuilder {\n public target:IEvent;\n public client:ExceptionlessClient;\n public pluginContextData:ContextData;\n\n private _validIdentifierErrorMessage:string = 'must contain between 8 and 100 alphanumeric or \\'-\\' characters.'; // optimization for minifier.\n\n constructor(event:IEvent, client:ExceptionlessClient, pluginContextData?:ContextData) {\n this.target = event;\n this.client = client;\n this.pluginContextData = pluginContextData || new ContextData();\n }\n\n public setType(type:string): EventBuilder {\n if (!!type) {\n this.target.type = type;\n }\n\n return this;\n }\n\n public setSource(source:string): EventBuilder {\n if (!!source) {\n this.target.source = source;\n }\n\n return this;\n }\n\n public setSessionId(sessionId:string): EventBuilder {\n if (!this.isValidIdentifier(sessionId)) {\n throw new Error(`SessionId ${this._validIdentifierErrorMessage}`);\n }\n\n this.target.session_id = sessionId;\n return this;\n }\n\n public setReferenceId(referenceId:string): EventBuilder {\n if (!this.isValidIdentifier(referenceId)) {\n throw new Error(`ReferenceId ${this._validIdentifierErrorMessage}`);\n }\n\n this.target.reference_id = referenceId;\n return this;\n }\n\n public setMessage(message:string): EventBuilder {\n if (!!message) {\n this.target.message = message;\n }\n\n return this;\n }\n\n public setGeo(latitude: number, longitude: number): EventBuilder {\n if (latitude < -90.0 || latitude > 90.0) {\n throw new Error('Must be a valid latitude value between -90.0 and 90.0.');\n }\n\n if (longitude < -180.0 || longitude > 180.0) {\n throw new Error('Must be a valid longitude value between -180.0 and 180.0.');\n }\n\n this.target.geo = `${latitude},${longitude}`;\n return this;\n }\n\n public setUserIdentity(userInfo:IUserInfo): EventBuilder;\n public setUserIdentity(identity:string): EventBuilder;\n public setUserIdentity(identity:string, name:string): EventBuilder;\n public setUserIdentity(userInfoOrIdentity:IUserInfo|string, name?:string): EventBuilder {\n let userInfo = typeof userInfoOrIdentity !== 'string' ? userInfoOrIdentity : { identity: userInfoOrIdentity, name: name };\n if (!userInfo || (!userInfo.identity && !userInfo.name)) {\n return this;\n }\n\n this.setProperty('@user', userInfo);\n return this;\n }\n\n public setValue(value:number): EventBuilder {\n if (!!value) {\n this.target.value = value;\n }\n\n return this;\n }\n\n public addTags(...tags:string[]): EventBuilder {\n this.target.tags = Utils.addRange(this.target.tags, ...tags);\n return this;\n }\n\n public setProperty(name:string, value:any): EventBuilder {\n if (!name || (value === undefined || value == null)) {\n return this;\n }\n\n if (!this.target.data) {\n this.target.data = {};\n }\n\n this.target.data[name] = value;\n return this;\n }\n\n public markAsCritical(critical:boolean): EventBuilder {\n if (critical) {\n this.addTags('Critical');\n }\n\n return this;\n }\n\n public addRequestInfo(request:Object): EventBuilder {\n if (!!request) {\n this.pluginContextData['@request'] = request;\n }\n\n return this;\n }\n\n public submit(callback?:(context:EventPluginContext) => void): void {\n this.client.submitEvent(this.target, this.pluginContextData, callback);\n }\n\n private isValidIdentifier(value:string): boolean {\n if (!value) {\n return true;\n }\n\n if (value.length < 8 || value.length > 100) {\n return false;\n }\n\n for (var index = 0; index < value.length; index++) {\n let code = value.charCodeAt(index);\n let isDigit = (code >= 48) && (code <= 57);\n let isLetter = ((code >= 65) && (code <= 90)) || ((code >= 97) && (code <= 122));\n let isMinus = code === 45;\n\n if (!(isDigit || isLetter) && !isMinus) {\n return false;\n }\n }\n\n return true;\n }\n}\n\nexport interface IUserDescription {\n email_address?:string;\n description?:string;\n data?:any;\n}\n\nexport class ContextData {\n public setException(exception:Error): void {\n if (exception) {\n this['@@_Exception'] = exception;\n }\n }\n\n public get hasException(): boolean {\n return !!this['@@_Exception'];\n }\n\n public getException(): Error {\n return this['@@_Exception'] || null;\n }\n\n public markAsUnhandledError(): void {\n this['@@_IsUnhandledError'] = true;\n }\n\n public get isUnhandledError(): boolean {\n return !!this['@@_IsUnhandledError'];\n }\n\n public setSubmissionMethod(method:string): void {\n if (method) {\n this['@@_SubmissionMethod'] = method;\n }\n }\n\n public getSubmissionMethod(): string {\n return this['@@_SubmissionMethod'] || null;\n }\n}\n\nexport class SubmissionResponse {\n success:boolean = false;\n badRequest:boolean = false;\n serviceUnavailable:boolean = false;\n paymentRequired:boolean = false;\n unableToAuthenticate:boolean = false;\n notFound:boolean = false;\n requestEntityTooLarge:boolean = false;\n statusCode:number;\n message:string;\n\n constructor(statusCode:number, message?:string) {\n this.statusCode = statusCode;\n this.message = message;\n\n this.success = statusCode >= 200 && statusCode <= 299;\n this.badRequest = statusCode === 400;\n this.serviceUnavailable = statusCode === 503;\n this.paymentRequired = statusCode === 402;\n this.unableToAuthenticate = statusCode === 401 || statusCode === 403;\n this.notFound = statusCode === 404;\n this.requestEntityTooLarge = statusCode === 413;\n }\n}\n\n \n\nexport class ExceptionlessClient {\n /**\n * The default ExceptionlessClient instance.\n * @type {ExceptionlessClient}\n * @private\n */\n private static _instance:ExceptionlessClient = null;\n\n public config:Configuration;\n\n constructor();\n constructor(settings:IConfigurationSettings);\n constructor(apiKey:string, serverUrl?:string);\n constructor(settingsOrApiKey?:IConfigurationSettings|string, serverUrl?:string) {\n if (typeof settingsOrApiKey !== 'object') {\n this.config = new Configuration(settingsOrApiKey);\n } else {\n this.config = new Configuration({ apiKey: settingsOrApiKey, serverUrl: serverUrl });\n }\n }\n\n public createException(exception:Error): EventBuilder {\n let pluginContextData = new ContextData();\n pluginContextData.setException(exception);\n return this.createEvent(pluginContextData).setType('error');\n }\n\n public submitException(exception:Error, callback?:(context:EventPluginContext) => void): void {\n this.createException(exception).submit(callback);\n }\n\n public createUnhandledException(exception:Error, submissionMethod?:string): EventBuilder {\n let builder = this.createException(exception);\n builder.pluginContextData.markAsUnhandledError();\n builder.pluginContextData.setSubmissionMethod(submissionMethod);\n\n return builder;\n }\n\n public submitUnhandledException(exception:Error, submissionMethod?:string, callback?:(context:EventPluginContext) => void) {\n this.createUnhandledException(exception, submissionMethod).submit(callback);\n }\n\n public createFeatureUsage(feature:string): EventBuilder {\n return this.createEvent().setType('usage').setSource(feature);\n }\n\n public submitFeatureUsage(feature:string, callback?:(context:EventPluginContext) => void): void {\n this.createFeatureUsage(feature).submit(callback);\n }\n\n public createLog(message:string): EventBuilder;\n public createLog(source:string, message:string): EventBuilder;\n public createLog(source:string, message:string, level:string): EventBuilder;\n public createLog(sourceOrMessage:string, message?:string, level?:string): EventBuilder {\n let builder = this.createEvent().setType('log');\n\n if (message && level) {\n builder = builder.setSource(sourceOrMessage).setMessage(message).setProperty('@level', level);\n } else if (message) {\n builder = builder.setSource(sourceOrMessage).setMessage(message);\n } else {\n // TODO: Look into using https://www.stevefenton.co.uk/Content/Blog/Date/201304/Blog/Obtaining-A-Class-Name-At-Runtime-In-TypeScript/\n let caller:any = arguments.callee.caller;\n builder = builder.setSource(caller && caller.name).setMessage(sourceOrMessage);\n }\n\n return builder;\n }\n\n public submitLog(message:string): void;\n public submitLog(source:string, message:string): void;\n public submitLog(source:string, message:string, level:string, callback?:(context:EventPluginContext) => void): void;\n public submitLog(sourceOrMessage:string, message?:string, level?:string, callback?:(context:EventPluginContext) => void): void {\n this.createLog(sourceOrMessage, message, level).submit(callback);\n }\n\n public createNotFound(resource:string): EventBuilder {\n return this.createEvent().setType('404').setSource(resource);\n }\n\n public submitNotFound(resource:string, callback?:(context:EventPluginContext) => void): void {\n this.createNotFound(resource).submit(callback);\n }\n\n public createSessionStart(sessionId:string): EventBuilder {\n return this.createEvent().setType('start').setSessionId(sessionId);\n }\n\n public submitSessionStart(sessionId:string, callback?:(context:EventPluginContext) => void): void {\n this.createSessionStart(sessionId).submit(callback);\n }\n\n public createSessionEnd(sessionId:string): EventBuilder {\n return this.createEvent().setType('end').setSessionId(sessionId);\n }\n\n public submitSessionEnd(sessionId:string, callback?:(context:EventPluginContext) => void): void {\n this.createSessionEnd(sessionId).submit(callback);\n }\n\n public createEvent(pluginContextData?:ContextData): EventBuilder {\n return new EventBuilder({ date: new Date() }, this, pluginContextData);\n }\n\n /**\n * Submits the event to be sent to the server.\n * @param event The event data.\n * @param pluginContextData Any contextual data objects to be used by Exceptionless plugins to gather default information for inclusion in the report information.\n * @param callback\n */\n public submitEvent(event:IEvent, pluginContextData?:ContextData, callback?:(context:EventPluginContext) => void): void {\n function cancelled(context:EventPluginContext) {\n if (!!context) {\n context.cancelled = true;\n }\n\n return !!callback && callback(context);\n }\n\n let context = new EventPluginContext(this, event, pluginContextData);\n if (!event) {\n return cancelled(context);\n }\n\n if (!this.config.enabled) {\n this.config.log.info('Event submission is currently disabled.');\n return cancelled(context);\n }\n\n if (!event.data) {\n event.data = {};\n }\n\n if (!event.tags || !event.tags.length) {\n event.tags = [];\n }\n\n EventPluginManager.run(context, function (ctx:EventPluginContext) {\n let ev = ctx.event;\n if (!ctx.cancelled) {\n // ensure all required data\n if (!ev.type || ev.type.length === 0) {\n ev.type = 'log';\n }\n\n if (!ev.date) {\n ev.date = new Date();\n }\n\n let config = ctx.client.config;\n config.queue.enqueue(ev);\n\n if (ev.reference_id && ev.reference_id.length > 0) {\n ctx.log.info(`Setting last reference id '${ev.reference_id}'`);\n config.lastReferenceIdManager.setLast(ev.reference_id);\n }\n }\n\n !!callback && callback(ctx);\n });\n }\n\n /**\n * Updates the user's email address and description of an event for the specified reference id.\n * @param referenceId The reference id of the event to update.\n * @param email The user's email address to set on the event.\n * @param description The user's description of the event.\n */\n public updateUserEmailAndDescription(referenceId:string, email:string, description:string, callback?:(response:SubmissionResponse) => void) {\n if (!referenceId || !email || !description || !this.config.enabled) {\n return !!callback && callback(new SubmissionResponse(500, 'cancelled'));\n }\n\n let userDescription:IUserDescription = { email_address: email, description: description };\n this.config.submissionClient.postUserDescription(referenceId, userDescription, this.config, (response:SubmissionResponse) => {\n if (!response.success) {\n this.config.log.error(`Failed to submit user email and description for event '${referenceId}': ${response.statusCode} ${response.message}`);\n }\n\n !!callback && callback(response);\n });\n }\n\n /**\n * Gets the last event client id that was submitted to the server.\n * @returns {string} The event client id.\n */\n public getLastReferenceId(): string {\n return this.config.lastReferenceIdManager.getLast();\n }\n\n /**\n * The default ExceptionlessClient instance.\n * @type {ExceptionlessClient}\n */\n public static get default() {\n if (ExceptionlessClient._instance === null) {\n ExceptionlessClient._instance = new ExceptionlessClient(null);\n }\n\n return ExceptionlessClient._instance;\n }\n}\n\nexport interface IModule {\n data?:any;\n\n module_id?:number;\n name?:string;\n version?:string;\n is_entry?:boolean;\n created_date?:Date;\n modified_date?:Date;\n}\n\nexport interface IRequestInfo {\n user_agent?:string;\n http_method?:string;\n is_secure?:boolean;\n host?:string;\n port?:number;\n path?:string;\n referrer?:string;\n client_ip_address?:string;\n cookies?:any;\n post_data?:any;\n query_string?:any;\n data?:any;\n}\n\nexport interface IEnvironmentInfo {\n processor_count?:number;\n total_physical_memory?:number;\n available_physical_memory?:number;\n command_line?:string;\n process_name?:string;\n process_id?:string;\n process_memory_size?:number;\n thread_id?:string;\n architecture?:string;\n o_s_name?:string;\n o_s_version?:string;\n ip_address?:string;\n machine_name?:string;\n install_id?:string;\n runtime_version?:string;\n data?:any;\n}\n\n \n\nexport class ConfigurationDefaultsPlugin implements IEventPlugin {\n public priority:number = 10;\n public name:string = 'ConfigurationDefaultsPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n let defaultTags:string[] = context.client.config.defaultTags || [];\n for (let index = 0; index < defaultTags.length; index++) {\n let tag = defaultTags[index];\n if (!!tag && context.event.tags.indexOf(tag) < 0) {\n context.event.tags.push(tag);\n }\n }\n\n let defaultData:Object = context.client.config.defaultData || {};\n for (let key in defaultData) {\n if (!!defaultData[key]) {\n context.event.data[key] = defaultData[key];\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class ErrorPlugin implements IEventPlugin {\n public priority:number = 30;\n public name:string = 'ErrorPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ERROR_KEY:string = '@error'; // optimization for minifier.\n\n let exception = context.contextData.getException();\n if (!!exception) {\n context.event.type = 'error';\n\n if (!context.event.data[ERROR_KEY]) {\n let parser = context.client.config.errorParser;\n if (!parser) {\n throw new Error('No error parser was defined.');\n }\n\n let result = parser.parse(context, exception);\n if (!!result) {\n context.event.data[ERROR_KEY] = result;\n }\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class ModuleInfoPlugin implements IEventPlugin {\n public priority:number = 40;\n public name:string = 'ModuleInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ERROR_KEY:string = '@error'; // optimization for minifier.\n\n let collector = context.client.config.moduleCollector;\n if (context.event.data[ERROR_KEY] && !context.event.data['@error'].modules && !!collector) {\n let modules:IModule[] = collector.getModules(context);\n if (modules && modules.length > 0) {\n context.event.data[ERROR_KEY].modules = modules;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class RequestInfoPlugin implements IEventPlugin {\n public priority:number = 60;\n public name:string = 'RequestInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const REQUEST_KEY:string = '@request'; // optimization for minifier.\n\n let collector = context.client.config.requestInfoCollector;\n if (!context.event.data[REQUEST_KEY] && !!collector) {\n let requestInfo:IRequestInfo = collector.getRequestInfo(context);\n if (!!requestInfo) {\n context.event.data[REQUEST_KEY] = requestInfo;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class EnvironmentInfoPlugin implements IEventPlugin {\n public priority:number = 70;\n public name:string = 'EnvironmentInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ENVIRONMENT_KEY:string = '@environment'; // optimization for minifier.\n\n let collector = context.client.config.environmentInfoCollector;\n if (!context.event.data[ENVIRONMENT_KEY] && collector) {\n let environmentInfo:IEnvironmentInfo = collector.getEnvironmentInfo(context);\n if (!!environmentInfo) {\n context.event.data[ENVIRONMENT_KEY] = environmentInfo;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class SubmissionMethodPlugin implements IEventPlugin {\n public priority:number = 100;\n public name:string = 'SubmissionMethodPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n let submissionMethod:string = context.contextData.getSubmissionMethod();\n if (!!submissionMethod) {\n context.event.data['@submission_method'] = submissionMethod;\n }\n\n next && next();\n }\n}\n\nexport interface IParameter {\n data?:any;\n generic_arguments?:string[];\n\n name?:string;\n type?:string;\n type_namespace?:string;\n}\n\n \n\nexport interface IMethod {\n data?:any;\n generic_arguments?:string[];\n parameters?:IParameter[];\n\n is_signature_target?:boolean;\n declaring_namespace?:string;\n declaring_type?:string;\n name?:string;\n module_id?:number;\n}\n\n \n\nexport interface IStackFrame extends IMethod {\n file_name?:string;\n line_number?:number;\n column?:number;\n}\n\n \n\nexport interface IInnerError {\n message?:string;\n type?:string;\n code?:string;\n data?:any;\n inner?:IInnerError;\n stack_trace?:IStackFrame[];\n target_method?:IMethod;\n}\n\n \n\nexport interface IError extends IInnerError {\n modules?:IModule[];\n}\n\nexport interface IStorageItem {\n created:number;\n path:string;\n value:T;\n}\n\nexport interface SubmissionCallback {\n (status: number, message: string, data?: string, headers?: Object): void;\n}\n\nexport interface SubmissionRequest {\n serverUrl: string;\n apiKey: string;\n userAgent: string;\n method: string;\n path: string;\n data: string;\n}\n\nexport class SettingsResponse {\n success:boolean = false;\n settings:any;\n settingsVersion:number = -1;\n message:string;\n exception:any;\n\n constructor(success:boolean, settings:any, settingsVersion:number = -1, exception:any = null, message:string = null) {\n this.success = success;\n this.settings = settings;\n this.settingsVersion = settingsVersion;\n this.exception = exception;\n this.message = message;\n }\n}\n\nexport interface IClientConfiguration {\n settings:Object;\n version:number;\n}\n\nimport os = require('os');\nimport nodestacktrace = require('stack-trace');\nimport path = require('path');\nimport https = require('https');\nimport url = require('url');\n \n\nexport class NodeEnvironmentInfoCollector implements IEnvironmentInfoCollector {\n public getEnvironmentInfo(context:EventPluginContext): IEnvironmentInfo {\n function getIpAddresses():string {\n let ips:string[] = [];\n let interfaces = os.networkInterfaces();\n Object.keys(interfaces).forEach((name) => {\n interfaces[name].forEach((iface:any) => {\n if ('IPv4' === iface.family && !iface.internal) {\n ips.push(iface.address);\n }\n });\n });\n\n return ips.join(', ');\n }\n\n if (!os) {\n return null;\n }\n\n let environmentInfo: IEnvironmentInfo = {\n processor_count: os.cpus().length,\n total_physical_memory: os.totalmem(),\n available_physical_memory: os.freemem(),\n command_line: process.argv.join(' '),\n process_name: (process.title || '').replace(/[\\uE000-\\uF8FF]/g, ''),\n process_id: process.pid + '',\n process_memory_size: process.memoryUsage().heapTotal,\n // thread_id: '',\n architecture: os.arch(),\n o_s_name: os.type(),\n o_s_version: os.release(),\n ip_address: getIpAddresses(),\n machine_name: os.hostname(),\n // install_id: '',\n runtime_version: process.version,\n data: {\n loadavg: os.loadavg(),\n platform: os.platform(),\n tmpdir: os.tmpdir(),\n uptime: os.uptime()\n }\n };\n\n if ((os).endianness) {\n environmentInfo.data.endianness = (os).endianness();\n }\n\n return environmentInfo;\n }\n}\n\n \n\nexport class NodeErrorParser implements IErrorParser {\n public parse(context:EventPluginContext, exception:Error): IError {\n function getStackFrames(stackFrames:any[]): IStackFrame[] {\n let frames:IStackFrame[] = [];\n\n for (let index = 0; index < stackFrames.length; index++) {\n let frame = stackFrames[index];\n frames.push({\n name: frame.getMethodName() || frame.getFunctionName(),\n // parameters: frame.args,\n file_name: frame.getFileName(),\n line_number: frame.getLineNumber() || 0,\n column: frame.getColumnNumber() || 0,\n declaring_type: frame.getTypeName(),\n data: {\n is_native: frame.isNative() || (!!frame.filename && frame.filename[0] !== '/' && frame.filename[0] !== '.')\n }\n });\n }\n\n return frames;\n }\n\n if (!nodestacktrace) {\n throw new Error('Unable to load the stack trace library.');\n }\n\n let stackFrames = nodestacktrace.parse(exception) || [];\n return {\n type: exception.name,\n message: exception.message,\n stack_trace: getStackFrames(stackFrames)\n };\n }\n}\n\n \n\nexport class NodeModuleCollector implements IModuleCollector {\n\n private initialized:boolean = false;\n private installedModules:{[id:string]: IModule} = {};\n\n public getModules(context:EventPluginContext): IModule[] {\n this.initialize();\n\n if (!require.main) {\n return [];\n }\n\n let modulePath = path.dirname(require.main.filename) + '/node_modules/';\n let pathLength = modulePath.length;\n\n let loadedKeys = Object.keys(require.cache);\n let loadedModules = {};\n\n loadedKeys.forEach(key => {\n let id = key.substr(pathLength);\n console.log(id);\n id = id.substr(0, id.indexOf('/'));\n loadedModules[id] = true;\n });\n\n return Object.keys(loadedModules)\n .map(key => this.installedModules[key])\n .filter(m => m !== undefined);\n }\n\n private initialize() {\n if (this.initialized) {\n return;\n }\n\n this.initialized = true;\n\n let output = child.spawnSync('npm', ['ls', '--depth=0', '--json']).stdout;\n\n if (!output) {\n return;\n }\n\n let json;\n try {\n json = JSON.parse(output.toString());\n } catch (e) {\n return;\n }\n\n let items = json.dependencies;\n if (!items) {\n return;\n }\n\n let id = 0;\n this.installedModules = {};\n\n Object.keys(items).forEach(key => {\n let item = items[key];\n let theModule = {\n module_id: id++,\n name: key,\n version: item.version\n };\n\n this.installedModules[key] = theModule;\n });\n }\n}\n\n \n\nexport class NodeRequestInfoCollector implements IRequestInfoCollector {\n getRequestInfo(context:EventPluginContext):IRequestInfo {\n const REQUEST_KEY:string = '@request'; // optimization for minifier.\n if (!context.contextData[REQUEST_KEY]) {\n return null;\n }\n\n let request = context.contextData[REQUEST_KEY];\n // TODO: include referrer\n let requestInfo:IRequestInfo = {\n client_ip_address: request.ip,\n user_agent: request.headers['user-agent'],\n is_secure: request.secure,\n http_method: request.method,\n host: request.hostname || request.host,\n path: request.path,\n post_data: request.body,\n cookies: Utils.getCookies((request || {}).headers.cookie),\n query_string: request.params\n };\n\n let host = request.headers.host;\n let port:number = host && parseInt(host.slice(host.indexOf(':') + 1), 10);\n if (port > 0) {\n requestInfo.port = port;\n }\n\n return requestInfo;\n }\n}\n\n \n\nexport class NodeSubmissionAdapter implements ISubmissionAdapter {\n public sendRequest(request:SubmissionRequest, callback:SubmissionCallback, isAppExiting?:boolean) {\n if (isAppExiting) {\n this.sendRequestSync(request, callback);\n return;\n }\n\n let parsedHost = url.parse(request.serverUrl);\n\n let options: https.RequestOptions = {\n auth: `client:${request.apiKey}`,\n headers: {},\n hostname: parsedHost.hostname,\n method: request.method,\n port: parsedHost.port && parseInt(parsedHost.port, 10),\n path: request.path\n };\n\n options.headers['User-Agent'] = request.userAgent;\n\n if (request.method === 'POST') {\n options.headers = {\n 'Content-Type': 'application/json',\n 'Content-Length': request.data.length\n };\n }\n\n let protocol = (parsedHost.protocol === 'https' ? https : http);\n let clientRequest: http.ClientRequest = protocol.request(options, (response: http.IncomingMessage) => {\n let body = '';\n response.setEncoding('utf8');\n response.on('data', (chunk) => body += chunk);\n response.on('end', () => this.complete(response, body, response.headers, callback));\n });\n\n clientRequest.on('error', (error: Error) => callback(500, error.message));\n clientRequest.end(request.data);\n }\n\n private complete(response: http.IncomingMessage, responseBody: string, responseHeaders: Object, callback: SubmissionCallback): void {\n let message: string;\n if (response.statusCode === 0) {\n message = 'Unable to connect to server.';\n } else if (response.statusCode < 200 || response.statusCode > 299) {\n message = response.statusMessage || (response).message;\n }\n\n callback(response.statusCode || 500, message, responseBody, responseHeaders);\n }\n\n private sendRequestSync(request: SubmissionRequest, callback: SubmissionCallback): void {\n let requestJson = JSON.stringify(request);\n let res = child.spawnSync(process.execPath, [require.resolve('./submitSync.js')],\n {\n input: requestJson,\n stdio: ['pipe', 'pipe', process.stderr]\n });\n\n let out = res.stdout.toString();\n let result = JSON.parse(out);\n\n callback(result.status, result.message, result.data, result.headers);\n }\n}\n\n \n\nconst EXIT: string = 'exit';\nconst UNCAUGHT_EXCEPTION: string = 'uncaughtException';\nconst SIGINT: string = 'SIGINT';\nconst SIGINT_CODE: number = 2;\n\nlet defaults = Configuration.defaults;\ndefaults.environmentInfoCollector = new NodeEnvironmentInfoCollector();\ndefaults.errorParser = new NodeErrorParser();\ndefaults.moduleCollector = new NodeModuleCollector();\ndefaults.requestInfoCollector = new NodeRequestInfoCollector();\ndefaults.submissionAdapter = new NodeSubmissionAdapter();\n\nfunction getListenerCount(emitter, event:string): number {\n if (emitter.listenerCount) {\n return emitter.listenerCount(event);\n }\n return require('events').listenerCount(emitter, event);\n}\n\n/*\n * Adding a event handler for 'uncaughtException' modifies the default\n * Node behavior, so it won't exit or log to the console. Instead,\n * we hijack the event emitter and forward the exception to the callback.\n */\nfunction onUncaughtException(callback: (error: Error) => void) {\n let originalEmit = process.emit;\n\n process.emit = function(type: string, error: Error) {\n if (type === UNCAUGHT_EXCEPTION) {\n callback(error);\n }\n\n return originalEmit.apply(this, arguments);\n };\n}\n\nonUncaughtException(function(error: Error) {\n ExceptionlessClient.default.submitUnhandledException(error, UNCAUGHT_EXCEPTION);\n});\n\n/*\n * We cannot hijack SIGINT, so if there are no other handlers,\n * we just reproduce default Node.js behavior by exiting.\n */\nprocess.on(SIGINT, function() {\n if (getListenerCount(process, SIGINT) <= 1) {\n process.exit(128 + SIGINT_CODE);\n }\n});\n\nprocess.on(EXIT, function(code: number) {\n /**\n * exit codes: https://nodejs.org/api/process.html#process_event_exit\n * From now on, only synchronous code may run. As soon as this method\n * ends, the application inevitably will exit.\n */\n function getExitCodeReason(exitCode: number): string {\n if (exitCode === 1) {\n return 'Uncaught Fatal Exception';\n }\n\n if (exitCode === 3) {\n return 'Internal JavaScript Parse Error';\n }\n\n if (exitCode === 4) {\n return 'Internal JavaScript Evaluation Failure';\n }\n\n if (exitCode === 5) {\n return 'Fatal Exception';\n }\n\n if (exitCode === 6) {\n return 'Non-function Internal Exception Handler ';\n }\n\n if (exitCode === 7) {\n return 'Internal Exception Handler Run-Time Failure';\n }\n\n if (exitCode === 8) {\n return 'Uncaught Exception';\n }\n\n if (exitCode === 9) {\n return 'Invalid Argument';\n }\n\n if (exitCode === 10) {\n return 'Internal JavaScript Run-Time Failure';\n }\n\n if (exitCode === 12) {\n return 'Invalid Debug Argument';\n }\n\n return null;\n }\n\n let client = ExceptionlessClient.default;\n let message = getExitCodeReason(code);\n\n if (message !== null) {\n client.submitLog(EXIT, message, 'Error');\n }\n\n client.config.queue.process(true);\n // Application will now exit.\n});\n\n(Error).stackTraceLimit = Infinity;\n\n"]} \ No newline at end of file +{"version":3,"file":"exceptionless.node.js","sourceRoot":"/source/","sources":["exceptionless.node.ts"],"names":["getListenerCount","onUncaughtException","getExitCodeReason"],"mappings":"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","sourcesContent":["import * as events from \"events\";\nimport * as net from \"net\";\nimport * as stream from \"stream\";\nimport * as child from \"child_process\";\nimport * as tls from \"tls\";\nimport * as http from \"http\";\nimport * as crypto from \"crypto\";\nexport interface IEvent {\n type?:string;\n source?:string;\n date?:Date;\n tags?:string[];\n message?:string;\n geo?:string;\n value?:number;\n data?:any;\n reference_id?:string;\n session_id?:string;\n}\n\nexport interface ILastReferenceIdManager {\n getLast(): string;\n clearLast(): void;\n setLast(eventId:string): void;\n}\n\nexport interface ILog {\n info(message:string):void;\n warn(message:string):void;\n error(message:string):void;\n}\n\n \n\nexport interface IEventQueue {\n enqueue(event:IEvent):void;\n process(isAppExiting?:boolean):void;\n suspendProcessing(durationInMinutes?:number, discardFutureQueuedItems?:boolean, clearQueue?:boolean):void;\n}\n\n \n\nexport interface IEnvironmentInfoCollector {\n getEnvironmentInfo(context:EventPluginContext):IEnvironmentInfo;\n}\n\n \n\nexport interface IErrorParser {\n parse(context:EventPluginContext, exception:Error): IError;\n}\n\n \n\nexport interface IModuleCollector {\n getModules(context:EventPluginContext):IModule[];\n}\n\n \n\nexport interface IRequestInfoCollector {\n getRequestInfo(context:EventPluginContext):IRequestInfo;\n}\n\n \n\nexport interface IStorage {\n save(path:string, value:T):boolean;\n get(path:string):T;\n getList(searchPattern?:string, limit?:number):IStorageItem[];\n remove(path:string):void;\n}\n\n \n\nexport interface ISubmissionAdapter {\n sendRequest(request:SubmissionRequest, callback:SubmissionCallback, isAppExiting?:boolean): void;\n}\n\n \n\nexport interface ISubmissionClient {\n postEvents(events:IEvent[], config:Configuration, callback:(response:SubmissionResponse) => void, isAppExiting?:boolean):void;\n postUserDescription(referenceId:string, description:IUserDescription, config:Configuration, callback:(response:SubmissionResponse) => void):void;\n getSettings(config:Configuration, callback:(response:SettingsResponse) => void):void;\n}\n\n \n\nexport interface IConfigurationSettings {\n apiKey?:string;\n serverUrl?:string;\n environmentInfoCollector?:IEnvironmentInfoCollector;\n errorParser?:IErrorParser;\n lastReferenceIdManager?:ILastReferenceIdManager;\n log?:ILog;\n moduleCollector?:IModuleCollector;\n requestInfoCollector?:IRequestInfoCollector;\n submissionBatchSize?:number;\n submissionClient?:ISubmissionClient;\n submissionAdapter?:ISubmissionAdapter;\n storage?:IStorage;\n queue?:IEventQueue;\n}\n\n \n\nexport class SettingsManager {\n /**\n * The configuration settings path.\n * @type {string}\n * @private\n */\n private static _configPath:string = 'ex-server-settings.json';\n\n /**\n * A list of handlers that will be fired when the settings change.\n * @type {Array}\n * @private\n */\n private static _handlers:{ (config:Configuration):void }[] = [];\n\n public static onChanged(handler:(config:Configuration) => void) {\n !!handler && this._handlers.push(handler);\n }\n\n public static applySavedServerSettings(config:Configuration):void {\n config.log.info('Applying saved settings.');\n config.settings = Utils.merge(config.settings, this.getSavedServerSettings(config));\n this.changed(config);\n }\n\n public static checkVersion(version:number, config:Configuration):void {\n if (version) {\n let savedConfigVersion = parseInt(config.storage.get(`${this._configPath}-version`), 10);\n if (isNaN(savedConfigVersion) || version > savedConfigVersion) {\n config.log.info(`Updating settings from v${(!isNaN(savedConfigVersion) ? savedConfigVersion : 0)} to v${version}`);\n this.updateSettings(config);\n }\n }\n }\n\n public static updateSettings(config:Configuration):void {\n if (!config.isValid) {\n config.log.error('Unable to update settings: ApiKey is not set.');\n return;\n }\n\n config.submissionClient.getSettings(config, (response:SettingsResponse) => {\n if (!response || !response.success || !response.settings) {\n return;\n }\n\n config.settings = Utils.merge(config.settings, response.settings);\n\n // TODO: Store snapshot of settings after reading from config and attributes and use that to revert to defaults.\n // Remove any existing server settings that are not in the new server settings.\n let savedServerSettings = SettingsManager.getSavedServerSettings(config);\n for (let key in savedServerSettings) {\n if (response.settings[key]) {\n continue;\n }\n\n delete config.settings[key];\n }\n\n let path = SettingsManager._configPath; // optimization for minifier.\n config.storage.save(`${path}-version`, response.settingsVersion);\n config.storage.save(path, response.settings);\n\n config.log.info('Updated settings');\n this.changed(config);\n });\n }\n\n private static changed(config:Configuration) {\n let handlers = this._handlers; // optimization for minifier.\n for (let index = 0; index < handlers.length; index++) {\n handlers[index](config);\n }\n }\n\n private static getSavedServerSettings(config:Configuration):Object {\n return config.storage.get(this._configPath) || {};\n }\n}\n\n \n\nexport class DefaultLastReferenceIdManager implements ILastReferenceIdManager {\n /**\n * Gets the last event's reference id that was submitted to the server.\n * @type {string}\n * @private\n */\n private _lastReferenceId:string = null;\n\n /**\n * Gets the last event's reference id that was submitted to the server.\n * @returns {string}\n */\n getLast(): string {\n return this._lastReferenceId;\n }\n\n /**\n * Clears the last event's reference id.\n */\n clearLast():void {\n this._lastReferenceId = null;\n }\n\n /**\n * Sets the last event's reference id.\n * @param eventId\n */\n setLast(eventId:string):void {\n this._lastReferenceId = eventId;\n }\n}\n\n \n\nexport class ConsoleLog implements ILog {\n public info(message:string):void {\n this.log('info', message);\n }\n\n public warn(message:string):void {\n this.log('warn', message);\n }\n\n public error(message:string):void {\n this.log('error', message);\n }\n\n private log(level:string, message:string) {\n if (console && console[level]) {\n console[level](`[${level}] Exceptionless: ${message}`);\n }\n }\n}\n\n \n\nexport class NullLog implements ILog {\n public info(message:string):void {}\n public warn(message:string):void {}\n public error(message:string):void {}\n}\n\nexport interface IUserInfo {\n identity?:string;\n name?:string;\n data?:any;\n}\n\n \n\nexport interface IEventPlugin {\n priority?:number;\n name?:string;\n run(context:EventPluginContext, next?:() => void): void;\n}\n\n \n\nexport class EventPluginContext {\n public cancelled:boolean;\n public client:ExceptionlessClient;\n public event:IEvent;\n public contextData:ContextData;\n\n constructor(client:ExceptionlessClient, event:IEvent, contextData?:ContextData) {\n this.client = client;\n this.event = event;\n this.contextData = contextData ? contextData : new ContextData();\n }\n\n public get log(): ILog {\n return this.client.config.log;\n }\n}\n\n \n\nexport class EventPluginManager {\n public static run(context:EventPluginContext, callback:(context?:EventPluginContext) => void): void {\n let wrap = function (plugin:IEventPlugin, next?:() => void): () => void {\n return () => {\n try {\n if (!context.cancelled) {\n plugin.run(context, next);\n }\n } catch (ex) {\n context.cancelled = true;\n context.log.error(`Error running plugin '${plugin.name}': ${ex.message}. Discarding Event.`);\n }\n\n if (context.cancelled && !!callback) {\n callback(context);\n }\n };\n };\n\n let plugins:IEventPlugin[] = context.client.config.plugins; // optimization for minifier.\n let wrappedPlugins:{ (): void }[] = [];\n if (!!callback) {\n wrappedPlugins[plugins.length] = wrap({ name: 'cb', priority: 9007199254740992, run: callback }, null);\n }\n\n for (let index = plugins.length - 1; index > -1; index--) {\n wrappedPlugins[index] = wrap(plugins[index], !!callback || (index < plugins.length - 1) ? wrappedPlugins[index + 1] : null);\n }\n\n wrappedPlugins[0]();\n }\n\n public static addDefaultPlugins(config:Configuration): void {\n config.addPlugin(new ConfigurationDefaultsPlugin());\n config.addPlugin(new ErrorPlugin());\n config.addPlugin(new ModuleInfoPlugin());\n config.addPlugin(new RequestInfoPlugin());\n config.addPlugin(new EnvironmentInfoPlugin());\n config.addPlugin(new SubmissionMethodPlugin());\n }\n}\n\n \n\nexport class ReferenceIdPlugin implements IEventPlugin {\n public priority:number = 20;\n public name:string = 'ReferenceIdPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n if ((!context.event.reference_id || context.event.reference_id.length === 0) && context.event.type === 'error') {\n context.event.reference_id = Utils.guid().replace('-', '').substring(0, 10);\n }\n\n next && next();\n }\n}\n\n \n\nexport class DefaultEventQueue implements IEventQueue {\n /**\n * The configuration object.\n * @type {Configuration}\n * @private\n */\n private _config:Configuration;\n\n /**\n * Suspends processing until the specified time.\n * @type {Date}\n * @private\n */\n private _suspendProcessingUntil:Date;\n\n /**\n * Discards queued items until the specified time.\n * @type {Date}\n * @private\n */\n private _discardQueuedItemsUntil:Date;\n\n /**\n * Returns true if the queue is processing.\n * @type {boolean}\n * @private\n */\n private _processingQueue:boolean = false;\n\n /**\n * Processes the queue every xx seconds.\n * @type {Timer}\n * @private\n */\n private _queueTimer:any;\n\n constructor(config:Configuration) {\n this._config = config;\n }\n\n public enqueue(event:IEvent): void {\n let config:Configuration = this._config; // Optimization for minifier.\n this.ensureQueueTimer();\n\n if (this.areQueuedItemsDiscarded()) {\n config.log.info('Queue items are currently being discarded. The event will not be queued.');\n return;\n }\n\n let key = `ex-q-${new Date().toJSON()}-${Utils.randomNumber()}`;\n config.log.info(`Enqueuing event: ${key} type=${event.type} ${!!event.reference_id ? 'refid=' + event.reference_id : ''}`);\n config.storage.save(key, event);\n }\n\n public process(isAppExiting?:boolean): void {\n function getEvents(events:{ path:string, value:IEvent }[]):IEvent[] {\n let items:IEvent[] = [];\n for (let index = 0; index < events.length; index++) {\n items.push(events[index].value);\n }\n\n return items;\n }\n\n const queueNotProcessed:string = 'The queue will not be processed.'; // optimization for minifier.\n let config:Configuration = this._config; // Optimization for minifier.\n let log:ILog = config.log; // Optimization for minifier.\n\n this.ensureQueueTimer();\n\n if (this._processingQueue) {\n return;\n }\n\n log.info('Processing queue...');\n if (!config.enabled) {\n log.info(`Configuration is disabled. ${queueNotProcessed}`);\n return;\n }\n\n if (!config.isValid) {\n log.info(`Invalid Api Key. ${queueNotProcessed}`);\n return;\n }\n\n this._processingQueue = true;\n\n try {\n let events = config.storage.getList('ex-q', config.submissionBatchSize);\n if (!events || events.length === 0) {\n this._processingQueue = false;\n return;\n }\n\n log.info(`Sending ${events.length} events to ${config.serverUrl}.`);\n config.submissionClient.postEvents(getEvents(events), config, (response:SubmissionResponse) => {\n this.processSubmissionResponse(response, events);\n log.info('Finished processing queue.');\n this._processingQueue = false;\n }, isAppExiting);\n } catch (ex) {\n log.error(`Error processing queue: ${ex}`);\n this.suspendProcessing();\n this._processingQueue = false;\n }\n }\n\n public suspendProcessing(durationInMinutes?:number, discardFutureQueuedItems?:boolean, clearQueue?:boolean): void {\n let config:Configuration = this._config; // Optimization for minifier.\n\n if (!durationInMinutes || durationInMinutes <= 0) {\n durationInMinutes = 5;\n }\n\n config.log.info(`Suspending processing for ${durationInMinutes} minutes.`);\n this._suspendProcessingUntil = new Date(new Date().getTime() + (durationInMinutes * 60000));\n\n if (discardFutureQueuedItems) {\n this._discardQueuedItemsUntil = new Date(new Date().getTime() + (durationInMinutes * 60000));\n }\n\n if (clearQueue) {\n // Account is over the limit and we want to ensure that the sample size being sent in will contain newer errors.\n this.removeEvents(config.storage.getList('ex-q'));\n }\n }\n\n private areQueuedItemsDiscarded(): boolean {\n return this._discardQueuedItemsUntil && this._discardQueuedItemsUntil > new Date();\n }\n\n private ensureQueueTimer(): void {\n if (!this._queueTimer) {\n this._queueTimer = setInterval(() => this.onProcessQueue(), 10000);\n }\n }\n\n private isQueueProcessingSuspended(): boolean {\n return this._suspendProcessingUntil && this._suspendProcessingUntil > new Date();\n }\n\n private onProcessQueue(): void {\n if (!this.isQueueProcessingSuspended() && !this._processingQueue) {\n this.process();\n }\n }\n\n private processSubmissionResponse(response:SubmissionResponse, events:{ path:string, value:IEvent }[]): void {\n const noSubmission:string = 'The event will not be submitted.'; // Optimization for minifier.\n let config:Configuration = this._config; // Optimization for minifier.\n let log:ILog = config.log; // Optimization for minifier.\n\n if (response.success) {\n log.info(`Sent ${events.length} events.`);\n this.removeEvents(events);\n return;\n }\n\n if (response.serviceUnavailable) {\n // You are currently over your rate limit or the servers are under stress.\n log.error('Server returned service unavailable.');\n this.suspendProcessing();\n return;\n }\n\n if (response.paymentRequired) {\n // If the organization over the rate limit then discard the event.\n log.info('Too many events have been submitted, please upgrade your plan.');\n this.suspendProcessing(null, true, true);\n return;\n }\n\n if (response.unableToAuthenticate) {\n // The api key was suspended or could not be authorized.\n log.info(`Unable to authenticate, please check your configuration. ${noSubmission}`);\n this.suspendProcessing(15);\n this.removeEvents(events);\n return;\n }\n\n if (response.notFound || response.badRequest) {\n // The service end point could not be found.\n log.error(`Error while trying to submit data: ${response.message}`);\n this.suspendProcessing(60 * 4);\n this.removeEvents(events);\n return;\n }\n\n if (response.requestEntityTooLarge) {\n let message = 'Event submission discarded for being too large.';\n if (config.submissionBatchSize > 1) {\n log.error(`${message} Retrying with smaller batch size.`);\n config.submissionBatchSize = Math.max(1, Math.round(config.submissionBatchSize / 1.5));\n } else {\n log.error(`${message} ${noSubmission}`);\n this.removeEvents(events);\n }\n\n return;\n }\n\n if (!response.success) {\n log.error(`Error submitting events: ${response.message || 'Please check the network tab for more info.'}`);\n this.suspendProcessing();\n }\n }\n\n private removeEvents(events:{ path:string, value:IEvent }[]) {\n for (let index = 0; index < (events || []).length; index++) {\n this._config.storage.remove(events[index].path);\n }\n }\n}\n\n \n\nexport class InMemoryStorage implements IStorage {\n private _items:IStorageItem[] = [];\n private _maxItems:number;\n\n constructor(maxItems?:number) {\n this._maxItems = maxItems > 0 ? maxItems : 250;\n }\n\n public save(path:string, value:T):boolean {\n if (!path || !value) {\n return false;\n }\n\n this.remove(path);\n if (this._items.push({ created: new Date().getTime(), path: path, value: value }) > this._maxItems) {\n this._items.shift();\n }\n\n return true;\n }\n\n public get(path:string):T {\n let item:IStorageItem = path ? this.getList(`^${path}$`, 1)[0] : null;\n return item ? item.value : null;\n }\n\n public getList(searchPattern?:string, limit?:number):IStorageItem[] {\n let items = this._items; // Optimization for minifier\n if (!searchPattern) {\n return items.slice(0, limit);\n }\n\n let regex = new RegExp(searchPattern);\n let results:IStorageItem[] = [];\n for (let index = 0; index < items.length; index++) {\n if (regex.test(items[index].path)) {\n results.push(items[index]);\n\n if (results.length >= limit) {\n break;\n }\n }\n }\n\n return results;\n }\n\n public remove(path:string):void {\n if (path) {\n let item = this.getList(`^${path}$`, 1)[0];\n if (item) {\n this._items.splice(this._items.indexOf(item), 1);\n }\n }\n }\n}\n\n \n\ndeclare var XDomainRequest:{ new (); create(); };\n\nexport class DefaultSubmissionClient implements ISubmissionClient {\n public configurationVersionHeader:string = 'x-exceptionless-configversion';\n\n public postEvents(events:IEvent[], config:Configuration, callback:(response:SubmissionResponse) => void, isAppExiting?:boolean):void {\n let data = Utils.stringify(events, config.dataExclusions);\n let request = this.createRequest(config, 'POST', '/api/v2/events', data);\n let cb = this.createSubmissionCallback(config, callback);\n\n return config.submissionAdapter.sendRequest(request, cb, isAppExiting);\n }\n\n public postUserDescription(referenceId:string, description:IUserDescription, config:Configuration, callback:(response:SubmissionResponse) => void):void {\n let path = `/api/v2/events/by-ref/${encodeURIComponent(referenceId)}/user-description`;\n let data = Utils.stringify(description, config.dataExclusions);\n let request = this.createRequest(config, 'POST', path, data);\n let cb = this.createSubmissionCallback(config, callback);\n\n return config.submissionAdapter.sendRequest(request, cb);\n }\n\n public getSettings(config:Configuration, callback:(response:SettingsResponse) => void):void {\n let request = this.createRequest(config, 'GET', '/api/v2/projects/config');\n let cb = (status, message, data?, headers?) => {\n if (status !== 200) {\n return callback(new SettingsResponse(false, null, -1, null, message));\n }\n\n let settings:IClientConfiguration;\n try {\n settings = JSON.parse(data);\n } catch (e) {\n config.log.error(`Unable to parse settings: '${data}'`);\n }\n\n if (!settings || isNaN(settings.version)) {\n return callback(new SettingsResponse(false, null, -1, null, 'Invalid configuration settings.'));\n }\n\n callback(new SettingsResponse(true, settings.settings || {}, settings.version));\n };\n\n return config.submissionAdapter.sendRequest(request, cb);\n }\n\n private createRequest(config: Configuration, method: string, path: string, data: string = null): SubmissionRequest {\n return {\n method,\n path,\n data,\n serverUrl: config.serverUrl,\n apiKey: config.apiKey,\n userAgent: config.userAgent\n };\n }\n\n private createSubmissionCallback(config:Configuration, callback:(response:SubmissionResponse) => void) {\n return (status, message, data?, headers?) => {\n let settingsVersion:number = headers && parseInt(headers[this.configurationVersionHeader], 10);\n SettingsManager.checkVersion(settingsVersion, config);\n\n callback(new SubmissionResponse(status, message));\n };\n }\n}\n\nexport class Utils {\n public static addRange(target:T[], ...values:T[]) {\n if (!target) {\n target = [];\n }\n\n if (!values || values.length === 0) {\n return target;\n }\n\n for (let index = 0; index < values.length; index++) {\n if (values[index] && target.indexOf(values[index]) < 0) {\n target.push(values[index]);\n }\n }\n\n return target;\n }\n\n public static getHashCode(source:string): string {\n if (!source || source.length === 0) {\n return null;\n }\n\n let hash:number = 0;\n for (let index = 0; index < source.length; index++) {\n let character = source.charCodeAt(index);\n hash = ((hash << 5) - hash) + character;\n hash |= 0;\n }\n\n return hash.toString();\n }\n\n public static getCookies(cookies:string): Object {\n let result:Object = {};\n\n let parts:string[] = (cookies || '').split('; ');\n for (let index = 0; index < parts.length; index++) {\n let cookie:string[] = parts[index].split('=');\n result[cookie[0]] = cookie[1];\n }\n\n return result;\n }\n\n public static guid(): string {\n function s4() {\n return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);\n }\n\n return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();\n }\n\n public static merge(defaultValues:Object, values:Object) {\n let result:Object = {};\n\n for (let key in defaultValues || {}) {\n if (!!defaultValues[key]) {\n result[key] = defaultValues[key];\n }\n }\n\n for (let key in values || {}) {\n if (!!values[key]) {\n result[key] = values[key];\n }\n }\n\n return result;\n }\n\n public static parseVersion(source:string): string {\n if (!source) {\n return null;\n }\n\n let versionRegex = /(v?((\\d+)\\.(\\d+)(\\.(\\d+))?)(?:-([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?(?:\\+([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?)/;\n let matches = versionRegex.exec(source);\n if (matches && matches.length > 0) {\n return matches[0];\n }\n\n return null;\n }\n\n public static parseQueryString(query:string) {\n if (!query || query.length === 0) {\n return null;\n }\n\n let pairs:string[] = query.split('&');\n if (pairs.length === 0) {\n return null;\n }\n\n let result:Object = {};\n for (let index = 0; index < pairs.length; index++) {\n let pair = pairs[index].split('=');\n result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);\n }\n\n return result;\n }\n\n public static randomNumber(): number {\n return Math.floor(Math.random() * 9007199254740992);\n }\n\n public static stringify(data:any, exclusions?:string[]): string {\n function checkForMatch(pattern:string, value:string): boolean {\n if (!pattern || !value || typeof value !== 'string') {\n return false;\n }\n\n let trim = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;\n pattern = pattern.toLowerCase().replace(trim, '');\n value = value.toLowerCase().replace(trim, '');\n\n if (pattern.length <= 0) {\n return false;\n }\n\n let startsWithWildcard:boolean = pattern[0] === '*';\n if (startsWithWildcard) {\n pattern = pattern.slice(1);\n }\n\n let endsWithWildcard:boolean = pattern[pattern.length - 1] === '*';\n if (endsWithWildcard) {\n pattern = pattern.substring(0, pattern.length - 1);\n }\n\n if (startsWithWildcard && endsWithWildcard) {\n return value.indexOf(pattern) !== -1;\n }\n\n if (startsWithWildcard) {\n return value.lastIndexOf(pattern) === (value.length - pattern.length);\n }\n\n if (endsWithWildcard) {\n return value.indexOf(pattern) === 0;\n }\n\n return value === pattern;\n }\n\n function stringifyImpl(obj:any, excludedKeys:string[]): string {\n let cache:string[] = [];\n return JSON.stringify(obj, function(key:string, value:any) {\n for (let index = 0; index < (excludedKeys || []).length; index++) {\n if (checkForMatch(excludedKeys[index], key)) {\n return;\n }\n }\n\n if (typeof value === 'object' && !!value) {\n if (cache.indexOf(value) !== -1) {\n // Circular reference found, discard key\n return;\n }\n\n cache.push(value);\n }\n\n return value;\n });\n }\n\n if (({}).toString.call(data) === '[object Array]') {\n let result = [];\n for (let index = 0; index < data.length; index++) {\n result[index] = JSON.parse(stringifyImpl(data[index], exclusions || []));\n }\n\n return JSON.stringify(result);\n }\n\n return stringifyImpl(data, exclusions || []);\n }\n}\n\n \n\nexport class Configuration implements IConfigurationSettings {\n /**\n * The default configuration settings that are applied to new configuration instances.\n * @type {IConfigurationSettings}\n * @private\n */\n private static _defaultSettings:IConfigurationSettings = null;\n\n /**\n * A default list of tags that will automatically be added to every\n * report submitted to the server.\n *\n * @type {Array}\n */\n public defaultTags:string[] = [];\n\n /**\n * A default list of of extended data objects that will automatically\n * be added to every report submitted to the server.\n *\n * @type {{}}\n */\n public defaultData:Object = {};\n\n /**\n * Whether the client is currently enabled or not. If it is disabled,\n * submitted errors will be discarded and no data will be sent to the server.\n *\n * @returns {boolean}\n */\n public enabled:boolean = true;\n\n public environmentInfoCollector:IEnvironmentInfoCollector;\n public errorParser:IErrorParser;\n public lastReferenceIdManager:ILastReferenceIdManager = new DefaultLastReferenceIdManager();\n public log:ILog;\n public moduleCollector:IModuleCollector;\n public requestInfoCollector:IRequestInfoCollector;\n\n /**\n * Maximum number of events that should be sent to the server together in a batch. (Defaults to 50)\n */\n public submissionBatchSize:number;\n public submissionAdapter:ISubmissionAdapter;\n public submissionClient:ISubmissionClient;\n\n /**\n * Contains a dictionary of custom settings that can be used to control\n * the client and will be automatically updated from the server.\n */\n public settings:Object = {};\n\n public storage:IStorage;\n\n public queue:IEventQueue;\n\n /**\n * The list of plugins that will be used in this configuration.\n * @type {Array}\n * @private\n */\n private _plugins:IEventPlugin[] = [];\n\n constructor(configSettings?:IConfigurationSettings) {\n function inject(fn:any) {\n return typeof fn === 'function' ? fn(this) : fn;\n }\n\n configSettings = Utils.merge(Configuration.defaults, configSettings);\n\n this.log = inject(configSettings.log) || new NullLog();\n this.apiKey = configSettings.apiKey;\n this.serverUrl = configSettings.serverUrl;\n\n this.environmentInfoCollector = inject(configSettings.environmentInfoCollector);\n this.errorParser = inject(configSettings.errorParser);\n this.lastReferenceIdManager = inject(configSettings.lastReferenceIdManager) || new DefaultLastReferenceIdManager();\n this.moduleCollector = inject(configSettings.moduleCollector);\n this.requestInfoCollector = inject(configSettings.requestInfoCollector);\n this.submissionBatchSize = inject(configSettings.submissionBatchSize) || 50;\n this.submissionAdapter = inject(configSettings.submissionAdapter);\n this.submissionClient = inject(configSettings.submissionClient) || new DefaultSubmissionClient();\n this.storage = inject(configSettings.storage) || new InMemoryStorage();\n this.queue = inject(configSettings.queue) || new DefaultEventQueue(this);\n\n SettingsManager.applySavedServerSettings(this);\n EventPluginManager.addDefaultPlugins(this);\n }\n\n /**\n * The API key that will be used when sending events to the server.\n * @type {string}\n * @private\n */\n private _apiKey:string;\n\n /**\n * The API key that will be used when sending events to the server.\n * @returns {string}\n */\n public get apiKey():string {\n return this._apiKey;\n }\n\n /**\n * The API key that will be used when sending events to the server.\n * @param value\n */\n public set apiKey(value:string) {\n this._apiKey = value || null;\n this.log.info(`apiKey: ${this._apiKey}`);\n }\n\n /**\n * Returns true if the apiKey is valid.\n * @returns {boolean}\n */\n public get isValid():boolean {\n return !!this.apiKey && this.apiKey.length >= 10;\n }\n\n /**\n * The server url that all events will be sent to.\n * @type {string}\n * @private\n */\n private _serverUrl:string = 'https://collector.exceptionless.io';\n\n /**\n * The server url that all events will be sent to.\n * @returns {string}\n */\n public get serverUrl():string {\n return this._serverUrl;\n }\n\n /**\n * The server url that all events will be sent to.\n * @param value\n */\n public set serverUrl(value:string) {\n if (!!value) {\n this._serverUrl = value;\n this.log.info(`serverUrl: ${this._serverUrl}`);\n }\n }\n\n /**\n * A list of exclusion patterns.\n * @type {Array}\n * @private\n */\n private _dataExclusions:string[] = [];\n\n /**\n * A list of exclusion patterns that will automatically remove any data that\n * matches them from any data submitted to the server.\n *\n * For example, entering CreditCard will remove any extended data properties,\n * form fields, cookies and query parameters from the report.\n *\n * @returns {string[]}\n */\n public get dataExclusions():string[] {\n let exclusions:string = this.settings['@@DataExclusions'];\n return this._dataExclusions.concat(exclusions && exclusions.split(',') || []);\n }\n\n /**\n * Add items to the list of exclusion patterns that will automatically remove any\n * data that matches them from any data submitted to the server.\n *\n * For example, entering CreditCard will remove any extended data properties, form\n * fields, cookies and query parameters from the report.\n *\n * @param exclusions\n */\n public addDataExclusions(...exclusions:string[]) {\n this._dataExclusions = Utils.addRange(this._dataExclusions, ...exclusions);\n }\n\n /**\n * The list of plugins that will be used in this configuration.\n * @returns {IEventPlugin[]}\n */\n public get plugins():IEventPlugin[] {\n return this._plugins.sort((p1:IEventPlugin, p2:IEventPlugin) => {\n return (p1.priority < p2.priority) ? -1 : (p1.priority > p2.priority) ? 1 : 0;\n });\n }\n\n /**\n * Register an plugin to be used in this configuration.\n * @param plugin\n */\n public addPlugin(plugin:IEventPlugin): void;\n\n /**\n * Register an plugin to be used in this configuration.\n * @param name The name used to identify the plugin.\n * @param priority Used to determine plugins priority.\n * @param pluginAction A function that is run.\n */\n public addPlugin(name:string, priority:number, pluginAction:(context:EventPluginContext, next?:() => void) => void): void;\n public addPlugin(pluginOrName:IEventPlugin|string, priority?:number, pluginAction?:(context:EventPluginContext, next?:() => void) => void): void {\n let plugin:IEventPlugin = !!pluginAction ? { name: pluginOrName, priority: priority, run: pluginAction } : pluginOrName;\n if (!plugin || !plugin.run) {\n this.log.error('Add plugin failed: Run method not defined');\n return;\n }\n\n if (!plugin.name) {\n plugin.name = Utils.guid();\n }\n\n if (!plugin.priority) {\n plugin.priority = 0;\n }\n\n let pluginExists:boolean = false;\n let plugins = this._plugins; // optimization for minifier.\n for (let index = 0; index < plugins.length; index++) {\n if (plugins[index].name === plugin.name) {\n pluginExists = true;\n break;\n }\n }\n\n if (!pluginExists) {\n plugins.push(plugin);\n }\n }\n\n /**\n * Remove the plugin from this configuration.\n * @param plugin\n */\n public removePlugin(plugin:IEventPlugin): void;\n\n /**\n * Remove an plugin by key from this configuration.\n * @param name\n */\n public removePlugin(name:string): void;\n public removePlugin(pluginOrName:IEventPlugin|string): void {\n let name:string = typeof pluginOrName === 'string' ? pluginOrName : pluginOrName.name;\n if (!name) {\n this.log.error('Remove plugin failed: Plugin name not defined');\n return;\n }\n\n let plugins = this._plugins; // optimization for minifier.\n for (let index = 0; index < plugins.length; index++) {\n if (plugins[index].name === name) {\n plugins.splice(index, 1);\n break;\n }\n }\n }\n\n /**\n * Automatically set the application version for events.\n * @param version\n */\n public setVersion(version:string): void {\n if (!!version) {\n this.defaultData['@version'] = version;\n }\n }\n\n public setUserIdentity(userInfo:IUserInfo): void;\n public setUserIdentity(identity:string): void;\n public setUserIdentity(identity:string, name:string): void;\n public setUserIdentity(userInfoOrIdentity:IUserInfo|string, name?:string): void {\n const USER_KEY:string = '@user'; // optimization for minifier.\n let userInfo:IUserInfo = typeof userInfoOrIdentity !== 'string' ? userInfoOrIdentity : { identity: userInfoOrIdentity, name: name };\n\n let shouldRemove:boolean = !userInfo || (!userInfo.identity && !userInfo.name);\n if (shouldRemove) {\n delete this.defaultData[USER_KEY];\n } else {\n this.defaultData[USER_KEY] = userInfo;\n }\n\n this.log.info(`user identity: ${shouldRemove ? 'null' : userInfo.identity}`);\n }\n\n /**\n * Used to identify the client that sent the events to the server.\n * @returns {string}\n */\n public get userAgent():string {\n return 'exceptionless-js/1.0.0.0';\n }\n\n /**\n * Automatically set a reference id for error events.\n */\n public useReferenceIds(): void {\n this.addPlugin(new ReferenceIdPlugin());\n }\n\n // TODO: Support a min log level.\n public useDebugLogger(): void {\n this.log = new ConsoleLog();\n }\n\n /**\n * The default configuration settings that are applied to new configuration instances.\n * @returns {IConfigurationSettings}\n */\n public static get defaults() {\n if (Configuration._defaultSettings === null) {\n Configuration._defaultSettings = {};\n }\n\n return Configuration._defaultSettings;\n }\n}\n\n \n\nexport class EventBuilder {\n public target:IEvent;\n public client:ExceptionlessClient;\n public pluginContextData:ContextData;\n\n private _validIdentifierErrorMessage:string = 'must contain between 8 and 100 alphanumeric or \\'-\\' characters.'; // optimization for minifier.\n\n constructor(event:IEvent, client:ExceptionlessClient, pluginContextData?:ContextData) {\n this.target = event;\n this.client = client;\n this.pluginContextData = pluginContextData || new ContextData();\n }\n\n public setType(type:string): EventBuilder {\n if (!!type) {\n this.target.type = type;\n }\n\n return this;\n }\n\n public setSource(source:string): EventBuilder {\n if (!!source) {\n this.target.source = source;\n }\n\n return this;\n }\n\n public setSessionId(sessionId:string): EventBuilder {\n if (!this.isValidIdentifier(sessionId)) {\n throw new Error(`SessionId ${this._validIdentifierErrorMessage}`);\n }\n\n this.target.session_id = sessionId;\n return this;\n }\n\n public setReferenceId(referenceId:string): EventBuilder {\n if (!this.isValidIdentifier(referenceId)) {\n throw new Error(`ReferenceId ${this._validIdentifierErrorMessage}`);\n }\n\n this.target.reference_id = referenceId;\n return this;\n }\n\n public setMessage(message:string): EventBuilder {\n if (!!message) {\n this.target.message = message;\n }\n\n return this;\n }\n\n public setGeo(latitude: number, longitude: number): EventBuilder {\n if (latitude < -90.0 || latitude > 90.0) {\n throw new Error('Must be a valid latitude value between -90.0 and 90.0.');\n }\n\n if (longitude < -180.0 || longitude > 180.0) {\n throw new Error('Must be a valid longitude value between -180.0 and 180.0.');\n }\n\n this.target.geo = `${latitude},${longitude}`;\n return this;\n }\n\n public setUserIdentity(userInfo:IUserInfo): EventBuilder;\n public setUserIdentity(identity:string): EventBuilder;\n public setUserIdentity(identity:string, name:string): EventBuilder;\n public setUserIdentity(userInfoOrIdentity:IUserInfo|string, name?:string): EventBuilder {\n let userInfo = typeof userInfoOrIdentity !== 'string' ? userInfoOrIdentity : { identity: userInfoOrIdentity, name: name };\n if (!userInfo || (!userInfo.identity && !userInfo.name)) {\n return this;\n }\n\n this.setProperty('@user', userInfo);\n return this;\n }\n\n public setValue(value:number): EventBuilder {\n if (!!value) {\n this.target.value = value;\n }\n\n return this;\n }\n\n public addTags(...tags:string[]): EventBuilder {\n this.target.tags = Utils.addRange(this.target.tags, ...tags);\n return this;\n }\n\n public setProperty(name:string, value:any): EventBuilder {\n if (!name || (value === undefined || value == null)) {\n return this;\n }\n\n if (!this.target.data) {\n this.target.data = {};\n }\n\n this.target.data[name] = value;\n return this;\n }\n\n public markAsCritical(critical:boolean): EventBuilder {\n if (critical) {\n this.addTags('Critical');\n }\n\n return this;\n }\n\n public addRequestInfo(request:Object): EventBuilder {\n if (!!request) {\n this.pluginContextData['@request'] = request;\n }\n\n return this;\n }\n\n public submit(callback?:(context:EventPluginContext) => void): void {\n this.client.submitEvent(this.target, this.pluginContextData, callback);\n }\n\n private isValidIdentifier(value:string): boolean {\n if (!value) {\n return true;\n }\n\n if (value.length < 8 || value.length > 100) {\n return false;\n }\n\n for (var index = 0; index < value.length; index++) {\n let code = value.charCodeAt(index);\n let isDigit = (code >= 48) && (code <= 57);\n let isLetter = ((code >= 65) && (code <= 90)) || ((code >= 97) && (code <= 122));\n let isMinus = code === 45;\n\n if (!(isDigit || isLetter) && !isMinus) {\n return false;\n }\n }\n\n return true;\n }\n}\n\nexport interface IUserDescription {\n email_address?:string;\n description?:string;\n data?:any;\n}\n\nexport class ContextData {\n public setException(exception:Error): void {\n if (exception) {\n this['@@_Exception'] = exception;\n }\n }\n\n public get hasException(): boolean {\n return !!this['@@_Exception'];\n }\n\n public getException(): Error {\n return this['@@_Exception'] || null;\n }\n\n public markAsUnhandledError(): void {\n this['@@_IsUnhandledError'] = true;\n }\n\n public get isUnhandledError(): boolean {\n return !!this['@@_IsUnhandledError'];\n }\n\n public setSubmissionMethod(method:string): void {\n if (method) {\n this['@@_SubmissionMethod'] = method;\n }\n }\n\n public getSubmissionMethod(): string {\n return this['@@_SubmissionMethod'] || null;\n }\n}\n\nexport class SubmissionResponse {\n success:boolean = false;\n badRequest:boolean = false;\n serviceUnavailable:boolean = false;\n paymentRequired:boolean = false;\n unableToAuthenticate:boolean = false;\n notFound:boolean = false;\n requestEntityTooLarge:boolean = false;\n statusCode:number;\n message:string;\n\n constructor(statusCode:number, message?:string) {\n this.statusCode = statusCode;\n this.message = message;\n\n this.success = statusCode >= 200 && statusCode <= 299;\n this.badRequest = statusCode === 400;\n this.serviceUnavailable = statusCode === 503;\n this.paymentRequired = statusCode === 402;\n this.unableToAuthenticate = statusCode === 401 || statusCode === 403;\n this.notFound = statusCode === 404;\n this.requestEntityTooLarge = statusCode === 413;\n }\n}\n\n \n\nexport class ExceptionlessClient {\n /**\n * The default ExceptionlessClient instance.\n * @type {ExceptionlessClient}\n * @private\n */\n private static _instance:ExceptionlessClient = null;\n\n public config:Configuration;\n\n constructor();\n constructor(settings:IConfigurationSettings);\n constructor(apiKey:string, serverUrl?:string);\n constructor(settingsOrApiKey?:IConfigurationSettings|string, serverUrl?:string) {\n if (typeof settingsOrApiKey !== 'object') {\n this.config = new Configuration(settingsOrApiKey);\n } else {\n this.config = new Configuration({ apiKey: settingsOrApiKey, serverUrl: serverUrl });\n }\n }\n\n public createException(exception:Error): EventBuilder {\n let pluginContextData = new ContextData();\n pluginContextData.setException(exception);\n return this.createEvent(pluginContextData).setType('error');\n }\n\n public submitException(exception:Error, callback?:(context:EventPluginContext) => void): void {\n this.createException(exception).submit(callback);\n }\n\n public createUnhandledException(exception:Error, submissionMethod?:string): EventBuilder {\n let builder = this.createException(exception);\n builder.pluginContextData.markAsUnhandledError();\n builder.pluginContextData.setSubmissionMethod(submissionMethod);\n\n return builder;\n }\n\n public submitUnhandledException(exception:Error, submissionMethod?:string, callback?:(context:EventPluginContext) => void) {\n this.createUnhandledException(exception, submissionMethod).submit(callback);\n }\n\n public createFeatureUsage(feature:string): EventBuilder {\n return this.createEvent().setType('usage').setSource(feature);\n }\n\n public submitFeatureUsage(feature:string, callback?:(context:EventPluginContext) => void): void {\n this.createFeatureUsage(feature).submit(callback);\n }\n\n public createLog(message:string): EventBuilder;\n public createLog(source:string, message:string): EventBuilder;\n public createLog(source:string, message:string, level:string): EventBuilder;\n public createLog(sourceOrMessage:string, message?:string, level?:string): EventBuilder {\n let builder = this.createEvent().setType('log');\n\n if (message && level) {\n builder = builder.setSource(sourceOrMessage).setMessage(message).setProperty('@level', level);\n } else if (message) {\n builder = builder.setSource(sourceOrMessage).setMessage(message);\n } else {\n // TODO: Look into using https://www.stevefenton.co.uk/Content/Blog/Date/201304/Blog/Obtaining-A-Class-Name-At-Runtime-In-TypeScript/\n let caller:any = arguments.callee.caller;\n builder = builder.setSource(caller && caller.name).setMessage(sourceOrMessage);\n }\n\n return builder;\n }\n\n public submitLog(message:string): void;\n public submitLog(source:string, message:string): void;\n public submitLog(source:string, message:string, level:string, callback?:(context:EventPluginContext) => void): void;\n public submitLog(sourceOrMessage:string, message?:string, level?:string, callback?:(context:EventPluginContext) => void): void {\n this.createLog(sourceOrMessage, message, level).submit(callback);\n }\n\n public createNotFound(resource:string): EventBuilder {\n return this.createEvent().setType('404').setSource(resource);\n }\n\n public submitNotFound(resource:string, callback?:(context:EventPluginContext) => void): void {\n this.createNotFound(resource).submit(callback);\n }\n\n public createSessionStart(sessionId:string): EventBuilder {\n return this.createEvent().setType('start').setSessionId(sessionId);\n }\n\n public submitSessionStart(sessionId:string, callback?:(context:EventPluginContext) => void): void {\n this.createSessionStart(sessionId).submit(callback);\n }\n\n public createSessionEnd(sessionId:string): EventBuilder {\n return this.createEvent().setType('end').setSessionId(sessionId);\n }\n\n public submitSessionEnd(sessionId:string, callback?:(context:EventPluginContext) => void): void {\n this.createSessionEnd(sessionId).submit(callback);\n }\n\n public createEvent(pluginContextData?:ContextData): EventBuilder {\n return new EventBuilder({ date: new Date() }, this, pluginContextData);\n }\n\n /**\n * Submits the event to be sent to the server.\n * @param event The event data.\n * @param pluginContextData Any contextual data objects to be used by Exceptionless plugins to gather default information for inclusion in the report information.\n * @param callback\n */\n public submitEvent(event:IEvent, pluginContextData?:ContextData, callback?:(context:EventPluginContext) => void): void {\n function cancelled(context:EventPluginContext) {\n if (!!context) {\n context.cancelled = true;\n }\n\n return !!callback && callback(context);\n }\n\n let context = new EventPluginContext(this, event, pluginContextData);\n if (!event) {\n return cancelled(context);\n }\n\n if (!this.config.enabled) {\n this.config.log.info('Event submission is currently disabled.');\n return cancelled(context);\n }\n\n if (!event.data) {\n event.data = {};\n }\n\n if (!event.tags || !event.tags.length) {\n event.tags = [];\n }\n\n EventPluginManager.run(context, function (ctx:EventPluginContext) {\n let ev = ctx.event;\n if (!ctx.cancelled) {\n // ensure all required data\n if (!ev.type || ev.type.length === 0) {\n ev.type = 'log';\n }\n\n if (!ev.date) {\n ev.date = new Date();\n }\n\n let config = ctx.client.config;\n config.queue.enqueue(ev);\n\n if (ev.reference_id && ev.reference_id.length > 0) {\n ctx.log.info(`Setting last reference id '${ev.reference_id}'`);\n config.lastReferenceIdManager.setLast(ev.reference_id);\n }\n }\n\n !!callback && callback(ctx);\n });\n }\n\n /**\n * Updates the user's email address and description of an event for the specified reference id.\n * @param referenceId The reference id of the event to update.\n * @param email The user's email address to set on the event.\n * @param description The user's description of the event.\n */\n public updateUserEmailAndDescription(referenceId:string, email:string, description:string, callback?:(response:SubmissionResponse) => void) {\n if (!referenceId || !email || !description || !this.config.enabled) {\n return !!callback && callback(new SubmissionResponse(500, 'cancelled'));\n }\n\n let userDescription:IUserDescription = { email_address: email, description: description };\n this.config.submissionClient.postUserDescription(referenceId, userDescription, this.config, (response:SubmissionResponse) => {\n if (!response.success) {\n this.config.log.error(`Failed to submit user email and description for event '${referenceId}': ${response.statusCode} ${response.message}`);\n }\n\n !!callback && callback(response);\n });\n }\n\n /**\n * Gets the last event client id that was submitted to the server.\n * @returns {string} The event client id.\n */\n public getLastReferenceId(): string {\n return this.config.lastReferenceIdManager.getLast();\n }\n\n /**\n * The default ExceptionlessClient instance.\n * @type {ExceptionlessClient}\n */\n public static get default() {\n if (ExceptionlessClient._instance === null) {\n ExceptionlessClient._instance = new ExceptionlessClient(null);\n }\n\n return ExceptionlessClient._instance;\n }\n}\n\nexport interface IModule {\n data?:any;\n\n module_id?:number;\n name?:string;\n version?:string;\n is_entry?:boolean;\n created_date?:Date;\n modified_date?:Date;\n}\n\nexport interface IRequestInfo {\n user_agent?:string;\n http_method?:string;\n is_secure?:boolean;\n host?:string;\n port?:number;\n path?:string;\n referrer?:string;\n client_ip_address?:string;\n cookies?:any;\n post_data?:any;\n query_string?:any;\n data?:any;\n}\n\nexport interface IEnvironmentInfo {\n processor_count?:number;\n total_physical_memory?:number;\n available_physical_memory?:number;\n command_line?:string;\n process_name?:string;\n process_id?:string;\n process_memory_size?:number;\n thread_id?:string;\n architecture?:string;\n o_s_name?:string;\n o_s_version?:string;\n ip_address?:string;\n machine_name?:string;\n install_id?:string;\n runtime_version?:string;\n data?:any;\n}\n\n \n\nexport class ConfigurationDefaultsPlugin implements IEventPlugin {\n public priority:number = 10;\n public name:string = 'ConfigurationDefaultsPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n let defaultTags:string[] = context.client.config.defaultTags || [];\n for (let index = 0; index < defaultTags.length; index++) {\n let tag = defaultTags[index];\n if (!!tag && context.event.tags.indexOf(tag) < 0) {\n context.event.tags.push(tag);\n }\n }\n\n let defaultData:Object = context.client.config.defaultData || {};\n for (let key in defaultData) {\n if (!!defaultData[key]) {\n context.event.data[key] = defaultData[key];\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class ErrorPlugin implements IEventPlugin {\n public priority:number = 30;\n public name:string = 'ErrorPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ERROR_KEY:string = '@error'; // optimization for minifier.\n\n let exception = context.contextData.getException();\n if (!!exception) {\n context.event.type = 'error';\n\n if (!context.event.data[ERROR_KEY]) {\n let parser = context.client.config.errorParser;\n if (!parser) {\n throw new Error('No error parser was defined.');\n }\n\n let result = parser.parse(context, exception);\n if (!!result) {\n context.event.data[ERROR_KEY] = result;\n }\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class ModuleInfoPlugin implements IEventPlugin {\n public priority:number = 40;\n public name:string = 'ModuleInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ERROR_KEY:string = '@error'; // optimization for minifier.\n\n let collector = context.client.config.moduleCollector;\n if (context.event.data[ERROR_KEY] && !context.event.data['@error'].modules && !!collector) {\n let modules:IModule[] = collector.getModules(context);\n if (modules && modules.length > 0) {\n context.event.data[ERROR_KEY].modules = modules;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class RequestInfoPlugin implements IEventPlugin {\n public priority:number = 60;\n public name:string = 'RequestInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const REQUEST_KEY:string = '@request'; // optimization for minifier.\n\n let collector = context.client.config.requestInfoCollector;\n if (!context.event.data[REQUEST_KEY] && !!collector) {\n let requestInfo:IRequestInfo = collector.getRequestInfo(context);\n if (!!requestInfo) {\n context.event.data[REQUEST_KEY] = requestInfo;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class EnvironmentInfoPlugin implements IEventPlugin {\n public priority:number = 70;\n public name:string = 'EnvironmentInfoPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n const ENVIRONMENT_KEY:string = '@environment'; // optimization for minifier.\n\n let collector = context.client.config.environmentInfoCollector;\n if (!context.event.data[ENVIRONMENT_KEY] && collector) {\n let environmentInfo:IEnvironmentInfo = collector.getEnvironmentInfo(context);\n if (!!environmentInfo) {\n context.event.data[ENVIRONMENT_KEY] = environmentInfo;\n }\n }\n\n next && next();\n }\n}\n\n \n\nexport class SubmissionMethodPlugin implements IEventPlugin {\n public priority:number = 100;\n public name:string = 'SubmissionMethodPlugin';\n\n public run(context:EventPluginContext, next?:() => void): void {\n let submissionMethod:string = context.contextData.getSubmissionMethod();\n if (!!submissionMethod) {\n context.event.data['@submission_method'] = submissionMethod;\n }\n\n next && next();\n }\n}\n\nexport interface IParameter {\n data?:any;\n generic_arguments?:string[];\n\n name?:string;\n type?:string;\n type_namespace?:string;\n}\n\n \n\nexport interface IMethod {\n data?:any;\n generic_arguments?:string[];\n parameters?:IParameter[];\n\n is_signature_target?:boolean;\n declaring_namespace?:string;\n declaring_type?:string;\n name?:string;\n module_id?:number;\n}\n\n \n\nexport interface IStackFrame extends IMethod {\n file_name?:string;\n line_number?:number;\n column?:number;\n}\n\n \n\nexport interface IInnerError {\n message?:string;\n type?:string;\n code?:string;\n data?:any;\n inner?:IInnerError;\n stack_trace?:IStackFrame[];\n target_method?:IMethod;\n}\n\n \n\nexport interface IError extends IInnerError {\n modules?:IModule[];\n}\n\nexport interface IStorageItem {\n created:number;\n path:string;\n value:T;\n}\n\nexport interface SubmissionCallback {\n (status: number, message: string, data?: string, headers?: Object): void;\n}\n\nexport interface SubmissionRequest {\n serverUrl: string;\n apiKey: string;\n userAgent: string;\n method: string;\n path: string;\n data: string;\n}\n\nexport class SettingsResponse {\n success:boolean = false;\n settings:any;\n settingsVersion:number = -1;\n message:string;\n exception:any;\n\n constructor(success:boolean, settings:any, settingsVersion:number = -1, exception:any = null, message:string = null) {\n this.success = success;\n this.settings = settings;\n this.settingsVersion = settingsVersion;\n this.exception = exception;\n this.message = message;\n }\n}\n\nexport interface IClientConfiguration {\n settings:Object;\n version:number;\n}\n\nimport os = require('os');\nimport nodestacktrace = require('stack-trace');\nimport path = require('path');\nimport https = require('https');\nimport url = require('url');\n \n\nexport class NodeEnvironmentInfoCollector implements IEnvironmentInfoCollector {\n public getEnvironmentInfo(context:EventPluginContext): IEnvironmentInfo {\n function getIpAddresses():string {\n let ips:string[] = [];\n let interfaces = os.networkInterfaces();\n Object.keys(interfaces).forEach((name) => {\n interfaces[name].forEach((iface:any) => {\n if ('IPv4' === iface.family && !iface.internal) {\n ips.push(iface.address);\n }\n });\n });\n\n return ips.join(', ');\n }\n\n if (!os) {\n return null;\n }\n\n let environmentInfo: IEnvironmentInfo = {\n processor_count: os.cpus().length,\n total_physical_memory: os.totalmem(),\n available_physical_memory: os.freemem(),\n command_line: process.argv.join(' '),\n process_name: (process.title || '').replace(/[\\uE000-\\uF8FF]/g, ''),\n process_id: process.pid + '',\n process_memory_size: process.memoryUsage().heapTotal,\n // thread_id: '',\n architecture: os.arch(),\n o_s_name: os.type(),\n o_s_version: os.release(),\n ip_address: getIpAddresses(),\n machine_name: os.hostname(),\n // install_id: '',\n runtime_version: process.version,\n data: {\n loadavg: os.loadavg(),\n platform: os.platform(),\n tmpdir: os.tmpdir(),\n uptime: os.uptime()\n }\n };\n\n if ((os).endianness) {\n environmentInfo.data.endianness = (os).endianness();\n }\n\n return environmentInfo;\n }\n}\n\n \n\nexport class NodeErrorParser implements IErrorParser {\n public parse(context:EventPluginContext, exception:Error): IError {\n function getStackFrames(stackFrames:any[]): IStackFrame[] {\n let frames:IStackFrame[] = [];\n\n for (let index = 0; index < stackFrames.length; index++) {\n let frame = stackFrames[index];\n frames.push({\n name: frame.getMethodName() || frame.getFunctionName(),\n // parameters: frame.args,\n file_name: frame.getFileName(),\n line_number: frame.getLineNumber() || 0,\n column: frame.getColumnNumber() || 0,\n declaring_type: frame.getTypeName(),\n data: {\n is_native: frame.isNative() || (!!frame.filename && frame.filename[0] !== '/' && frame.filename[0] !== '.')\n }\n });\n }\n\n return frames;\n }\n\n if (!nodestacktrace) {\n throw new Error('Unable to load the stack trace library.');\n }\n\n let stackFrames = nodestacktrace.parse(exception) || [];\n return {\n type: exception.name,\n message: exception.message,\n stack_trace: getStackFrames(stackFrames)\n };\n }\n}\n\n \n\nexport class NodeModuleCollector implements IModuleCollector {\n\n private initialized:boolean = false;\n private installedModules:{[id:string]: IModule} = {};\n\n public getModules(context:EventPluginContext): IModule[] {\n this.initialize();\n\n if (!require.main) {\n return [];\n }\n\n let modulePath = path.dirname(require.main.filename) + '/node_modules/';\n let pathLength = modulePath.length;\n\n let loadedKeys = Object.keys(require.cache);\n let loadedModules = {};\n\n loadedKeys.forEach(key => {\n let id = key.substr(pathLength);\n id = id.substr(0, id.indexOf('/'));\n loadedModules[id] = true;\n });\n\n return Object.keys(loadedModules)\n .map(key => this.installedModules[key])\n .filter(m => m !== undefined);\n }\n\n private initialize() {\n if (this.initialized) {\n return;\n }\n\n this.initialized = true;\n\n let output = child.spawnSync('npm', ['ls', '--depth=0', '--json']).stdout;\n\n if (!output) {\n return;\n }\n\n let json;\n try {\n json = JSON.parse(output.toString());\n } catch (e) {\n return;\n }\n\n let items = json.dependencies;\n if (!items) {\n return;\n }\n\n let id = 0;\n this.installedModules = {};\n\n Object.keys(items).forEach(key => {\n let item = items[key];\n let theModule = {\n module_id: id++,\n name: key,\n version: item.version\n };\n\n this.installedModules[key] = theModule;\n });\n }\n}\n\n \n\nexport class NodeRequestInfoCollector implements IRequestInfoCollector {\n getRequestInfo(context:EventPluginContext):IRequestInfo {\n const REQUEST_KEY:string = '@request'; // optimization for minifier.\n if (!context.contextData[REQUEST_KEY]) {\n return null;\n }\n\n let request = context.contextData[REQUEST_KEY];\n // TODO: include referrer\n let requestInfo:IRequestInfo = {\n client_ip_address: request.ip,\n user_agent: request.headers['user-agent'],\n is_secure: request.secure,\n http_method: request.method,\n host: request.hostname || request.host,\n path: request.path,\n post_data: request.body,\n cookies: Utils.getCookies((request || {}).headers.cookie),\n query_string: request.params\n };\n\n let host = request.headers.host;\n let port:number = host && parseInt(host.slice(host.indexOf(':') + 1), 10);\n if (port > 0) {\n requestInfo.port = port;\n }\n\n return requestInfo;\n }\n}\n\n \n\nexport class NodeSubmissionAdapter implements ISubmissionAdapter {\n public sendRequest(request:SubmissionRequest, callback:SubmissionCallback, isAppExiting?:boolean) {\n if (isAppExiting) {\n this.sendRequestSync(request, callback);\n return;\n }\n\n let parsedHost = url.parse(request.serverUrl);\n\n let options: https.RequestOptions = {\n auth: `client:${request.apiKey}`,\n headers: {},\n hostname: parsedHost.hostname,\n method: request.method,\n port: parsedHost.port && parseInt(parsedHost.port, 10),\n path: request.path\n };\n\n options.headers['User-Agent'] = request.userAgent;\n\n if (request.method === 'POST') {\n options.headers = {\n 'Content-Type': 'application/json',\n 'Content-Length': request.data.length\n };\n }\n\n let protocol = (parsedHost.protocol === 'https' ? https : http);\n let clientRequest: http.ClientRequest = protocol.request(options, (response: http.IncomingMessage) => {\n let body = '';\n response.setEncoding('utf8');\n response.on('data', (chunk) => body += chunk);\n response.on('end', () => this.complete(response, body, response.headers, callback));\n });\n\n clientRequest.on('error', (error: Error) => callback(500, error.message));\n clientRequest.end(request.data);\n }\n\n private complete(response: http.IncomingMessage, responseBody: string, responseHeaders: Object, callback: SubmissionCallback): void {\n let message: string;\n if (response.statusCode === 0) {\n message = 'Unable to connect to server.';\n } else if (response.statusCode < 200 || response.statusCode > 299) {\n message = response.statusMessage || (response).message;\n }\n\n callback(response.statusCode || 500, message, responseBody, responseHeaders);\n }\n\n private sendRequestSync(request: SubmissionRequest, callback: SubmissionCallback): void {\n let requestJson = JSON.stringify(request);\n let res = child.spawnSync(process.execPath, [require.resolve('./submitSync.js')],\n {\n input: requestJson,\n stdio: ['pipe', 'pipe', process.stderr]\n });\n\n let out = res.stdout.toString();\n let result = JSON.parse(out);\n\n callback(result.status, result.message, result.data, result.headers);\n }\n}\n\n \n\nconst EXIT: string = 'exit';\nconst UNCAUGHT_EXCEPTION: string = 'uncaughtException';\nconst SIGINT: string = 'SIGINT';\nconst SIGINT_CODE: number = 2;\n\nlet defaults = Configuration.defaults;\ndefaults.environmentInfoCollector = new NodeEnvironmentInfoCollector();\ndefaults.errorParser = new NodeErrorParser();\ndefaults.moduleCollector = new NodeModuleCollector();\ndefaults.requestInfoCollector = new NodeRequestInfoCollector();\ndefaults.submissionAdapter = new NodeSubmissionAdapter();\n\nfunction getListenerCount(emitter, event:string): number {\n if (emitter.listenerCount) {\n return emitter.listenerCount(event);\n }\n return require('events').listenerCount(emitter, event);\n}\n\n/*\n * Adding a event handler for 'uncaughtException' modifies the default\n * Node behavior, so it won't exit or log to the console. Instead,\n * we hijack the event emitter and forward the exception to the callback.\n */\nfunction onUncaughtException(callback: (error: Error) => void) {\n let originalEmit = process.emit;\n\n process.emit = function(type: string, error: Error) {\n if (type === UNCAUGHT_EXCEPTION) {\n callback(error);\n }\n\n return originalEmit.apply(this, arguments);\n };\n}\n\nonUncaughtException(function(error: Error) {\n ExceptionlessClient.default.submitUnhandledException(error, UNCAUGHT_EXCEPTION);\n});\n\n/*\n * We cannot hijack SIGINT, so if there are no other handlers,\n * we just reproduce default Node.js behavior by exiting.\n */\nprocess.on(SIGINT, function() {\n if (getListenerCount(process, SIGINT) <= 1) {\n process.exit(128 + SIGINT_CODE);\n }\n});\n\nprocess.on(EXIT, function(code: number) {\n /**\n * exit codes: https://nodejs.org/api/process.html#process_event_exit\n * From now on, only synchronous code may run. As soon as this method\n * ends, the application inevitably will exit.\n */\n function getExitCodeReason(exitCode: number): string {\n if (exitCode === 1) {\n return 'Uncaught Fatal Exception';\n }\n\n if (exitCode === 3) {\n return 'Internal JavaScript Parse Error';\n }\n\n if (exitCode === 4) {\n return 'Internal JavaScript Evaluation Failure';\n }\n\n if (exitCode === 5) {\n return 'Fatal Exception';\n }\n\n if (exitCode === 6) {\n return 'Non-function Internal Exception Handler ';\n }\n\n if (exitCode === 7) {\n return 'Internal Exception Handler Run-Time Failure';\n }\n\n if (exitCode === 8) {\n return 'Uncaught Exception';\n }\n\n if (exitCode === 9) {\n return 'Invalid Argument';\n }\n\n if (exitCode === 10) {\n return 'Internal JavaScript Run-Time Failure';\n }\n\n if (exitCode === 12) {\n return 'Invalid Debug Argument';\n }\n\n return null;\n }\n\n let client = ExceptionlessClient.default;\n let message = getExitCodeReason(code);\n\n if (message !== null) {\n client.submitLog(EXIT, message, 'Error');\n }\n\n client.config.queue.process(true);\n // Application will now exit.\n});\n\n(Error).stackTraceLimit = Infinity;\n\n"]} \ No newline at end of file diff --git a/src/services/NodeModuleCollector.ts b/src/services/NodeModuleCollector.ts index 53b7c867..3dedb324 100644 --- a/src/services/NodeModuleCollector.ts +++ b/src/services/NodeModuleCollector.ts @@ -26,7 +26,6 @@ export class NodeModuleCollector implements IModuleCollector { loadedKeys.forEach(key => { let id = key.substr(pathLength); - console.log(id); id = id.substr(0, id.indexOf('/')); loadedModules[id] = true; }); diff --git a/tslint.json b/tslint.json index 42a949fa..cb0f720c 100644 --- a/tslint.json +++ b/tslint.json @@ -21,7 +21,8 @@ "info", "time", "timeEnd", - "trace" + "trace", + "log" ], "no-construct": true, "no-debugger": true, From 48de7bdc05cae54a641b0e6b48f461557cda44eb Mon Sep 17 00:00:00 2001 From: Frank Ebersoll Date: Sat, 31 Oct 2015 04:17:26 +0100 Subject: [PATCH 5/7] tsproject aktualisiert --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d09b025b..819b3ca3 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "phantomjs": "^1.9.18", "rimraf": "2.4.3", "tracekit": "0.3.0", - "tsproject": "1.0.1", + "tsproject": "1.0.4", "typescript": "1.6.2" }, "dependencies": { From d42b526b538319378c6c88df52df06710a69176d Mon Sep 17 00:00:00 2001 From: Frank Ebersoll Date: Sat, 31 Oct 2015 04:19:21 +0100 Subject: [PATCH 6/7] ErrorPlugin: Additional Data Conflicts: dist/exceptionless.node.js.map --- dist/exceptionless.d.ts | 2 + dist/exceptionless.js | 40 +++ dist/exceptionless.js.map | 2 +- dist/exceptionless.min.js | 4 +- dist/exceptionless.min.js.map | 2 +- dist/exceptionless.node.js | 40 +++ dist/exceptionless.node.js.map | 2 +- .../default/ErrorPlugin-spec-exceptions.ts | 301 ++++++++++++++++++ src/plugins/default/ErrorPlugin-spec.ts | 76 +++++ src/plugins/default/ErrorPlugin.ts | 53 ++- 10 files changed, 513 insertions(+), 9 deletions(-) create mode 100644 src/plugins/default/ErrorPlugin-spec-exceptions.ts create mode 100644 src/plugins/default/ErrorPlugin-spec.ts diff --git a/dist/exceptionless.d.ts b/dist/exceptionless.d.ts index e37a18f9..cce444f1 100644 --- a/dist/exceptionless.d.ts +++ b/dist/exceptionless.d.ts @@ -334,7 +334,9 @@ export declare class ConfigurationDefaultsPlugin implements IEventPlugin { export declare class ErrorPlugin implements IEventPlugin { priority: number; name: string; + ignoredProperties: string[]; run(context: EventPluginContext, next?: () => void): void; + private getAdditionalData(exception); } export declare class ModuleInfoPlugin implements IEventPlugin { priority: number; diff --git a/dist/exceptionless.js b/dist/exceptionless.js index ea5e06b7..bc085199 100644 --- a/dist/exceptionless.js +++ b/dist/exceptionless.js @@ -2271,9 +2271,26 @@ var ErrorPlugin = (function () { function ErrorPlugin() { this.priority = 30; this.name = 'ErrorPlugin'; + this.ignoredProperties = [ + 'arguments', + 'column', + 'columnNumber', + 'description', + 'fileName', + 'message', + 'name', + 'number', + 'line', + 'lineNumber', + 'opera#sourceloc', + 'sourceURL', + 'stack', + 'stacktrace' + ]; } ErrorPlugin.prototype.run = function (context, next) { var ERROR_KEY = '@error'; + var EXTRA_PROPERTIES_KEY = '@ext'; var exception = context.contextData.getException(); if (!!exception) { context.event.type = 'error'; @@ -2284,12 +2301,35 @@ var ErrorPlugin = (function () { } var result = parser.parse(context, exception); if (!!result) { + var additionalData = this.getAdditionalData(exception); + if (!!additionalData) { + if (!result.data) { + result.data = {}; + } + result.data[EXTRA_PROPERTIES_KEY] = additionalData; + } context.event.data[ERROR_KEY] = result; } } } next && next(); }; + ErrorPlugin.prototype.getAdditionalData = function (exception) { + var _this = this; + var keys = Object.keys(exception) + .filter(function (key) { return _this.ignoredProperties.indexOf(key) < 0; }); + if (keys.length === 0) { + return null; + } + var additionalData = {}; + keys.forEach(function (key) { + var value = exception[key]; + if (typeof value !== 'function') { + additionalData[key] = value; + } + }); + return additionalData; + }; return ErrorPlugin; })(); exports.ErrorPlugin = ErrorPlugin; diff --git a/dist/exceptionless.js.map b/dist/exceptionless.js.map index 9f90d469..590ed98c 100644 --- a/dist/exceptionless.js.map +++ b/dist/exceptionless.js.map @@ -1 +1 @@ -{"version":3,"sources":["tracekit.js","/source/exceptionless.ts"],"names":["getDefaultsSettingsFromScriptTag","processUnhandledException"],"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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7oCA,8BAA8B,+BAA+B,CAAC,CAAA;AAqC9D,mCAAmC,+BAA+B,CAAC,CAAA;AACnE,uCAAuC,mCAAmC,CAAC,CAAA;AAC3E,4CAA4C,wCAAwC,CAAC,CAAA;AAIrF,yCAAyC,uCAAuC,CAAC,CAAA;AAKjF,oCAAoC,uBAAuB,CAAC,CAAA;AAC5D,sBAAsB,SAAS,CAAC,CAAA;AAEhC;IACEA,EAAEA,CAACA,CAACA,CAACA,QAAQA,IAAIA,CAACA,QAAQA,CAACA,oBAAoBA,CAACA,CAACA,CAACA;QAChDA,MAAMA,CAACA,IAAIA,CAACA;IACdA,CAACA;IAEDA,IAAIA,OAAOA,GAAGA,QAAQA,CAACA,oBAAoBA,CAACA,QAAQA,CAACA,CAACA;IACtDA,GAAGA,CAACA,CAACA,GAAGA,CAACA,KAAKA,GAAGA,CAACA,EAAEA,KAAKA,GAAGA,OAAOA,CAACA,MAAMA,EAAEA,KAAKA,EAAEA,EAAEA,CAACA;QACpDA,EAAEA,CAACA,CAACA,OAAOA,CAACA,KAAKA,CAACA,CAACA,GAAGA,IAAIA,OAAOA,CAACA,KAAKA,CAACA,CAACA,GAAGA,CAACA,OAAOA,CAACA,gBAAgBA,CAACA,GAAGA,CAACA,CAACA,CAACA,CAACA,CAACA;YAC5EA,MAAMA,CAACA,aAAKA,CAACA,gBAAgBA,CAACA,OAAOA,CAACA,KAAKA,CAACA,CAACA,GAAGA,CAACA,KAAKA,CAACA,GAAGA,CAACA,CAACA,GAAGA,EAAEA,CAACA,CAACA;QACrEA,CAACA;IACHA,CAACA;IACDA,MAAMA,CAACA,IAAIA,CAACA;AACdA,CAACA;AAED,mCAAmC,UAA8B,EAAE,OAAY;IAC7EC,IAAIA,OAAOA,GAAGA,yCAAmBA,CAACA,OAAOA,CAACA,wBAAwBA,CAACA,IAAIA,KAAKA,CAACA,UAAUA,CAACA,OAAOA,IAAIA,CAACA,OAAOA,IAAIA,EAAEA,CAACA,CAACA,MAAMA,IAAIA,cAAcA,CAACA,EAAEA,SAASA,CAACA,CAACA;IACzJA,OAAOA,CAACA,iBAAiBA,CAACA,wBAAwBA,CAACA,GAAGA,UAAUA,CAACA;IACjEA,OAAOA,CAACA,MAAMA,EAAEA,CAACA;AACnBA,CAACA;AAmBD,IAAI,QAAQ,GAAG,6BAAa,CAAC,QAAQ,CAAC;AACtC,IAAI,QAAQ,GAAG,gCAAgC,EAAE,CAAC;AAClD,EAAE,CAAC,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACxD,QAAQ,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;IAClC,QAAQ,CAAC,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC;AAC1C,CAAC;AAED,QAAQ,CAAC,WAAW,GAAG,IAAI,uCAAkB,EAAE,CAAC;AAChD,QAAQ,CAAC,eAAe,GAAG,IAAI,+CAAsB,EAAE,CAAC;AACxD,QAAQ,CAAC,oBAAoB,GAAG,IAAI,yDAA2B,EAAE,CAAC;AAClE,QAAQ,CAAC,iBAAiB,GAAG,IAAI,mDAAwB,EAAE,CAAC;AAE5D,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,yBAAyB,CAAC,CAAC;AACrD,QAAQ,CAAC,6BAA6B,EAAE,CAAC;AAUnC,KAAM,CAAC,eAAe,GAAG,QAAQ,CAAC","file":"exceptionless.js","sourcesContent":["/*\n TraceKit - Cross browser 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 'stack': [location]\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 var domain = '';\n try { domain = document.domain; } catch (e) {}\n if (url.indexOf(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|native|eval).*?)(?::(\\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 = chrome.exec(lines[i]))) {\n var isNative = parts[2] && parts[2].indexOf('native') !== -1;\n element = {\n 'url': !isNative ? parts[2] : null,\n 'func': parts[1] || UNKNOWN_FUNCTION,\n 'args': isNative ? [parts[2]] : [],\n 'line': parts[3] ? +parts[3] : null,\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 'args': [],\n 'line': +parts[3],\n 'column': parts[4] ? +parts[4] : null\n };\n } else 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] ? +parts[4] : null,\n 'column': parts[5] ? +parts[5] : 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 'stack': stack\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 if (!stacktrace) {\n return;\n }\n\n var opera10Regex = / line (\\d+).*script (?:in )?(\\S+)(?:: in function (\\S+))?$/i,\n opera11Regex = / line (\\d+), column (\\d+)\\s*(?:in (?:]+)>|([^\\)]+))\\((.*)\\))? in (.*):\\s*$/i,\n lines = stacktrace.split('\\n'),\n stack = [],\n parts;\n\n for (var line = 0; line < lines.length; line += 2) {\n var element = null;\n if ((parts = opera10Regex.exec(lines[line]))) {\n element = {\n 'url': parts[2],\n 'line': +parts[1],\n 'column': null,\n 'func': parts[3],\n 'args':[]\n };\n } else if ((parts = opera11Regex.exec(lines[line]))) {\n element = {\n 'url': parts[6],\n 'line': +parts[1],\n 'column': +parts[2],\n 'func': parts[3] || parts[4],\n 'args': parts[5] ? parts[5].split(',') : []\n };\n }\n\n if (element) {\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[line + 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 'stack': stack\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 // TODO: Clean this function up\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\n for (var s in scripts) {\n if (_has(scripts, s) && !scripts[s].src) {\n inlineScriptBlocks.push(scripts[s]);\n }\n }\n\n for (var line = 2; line < lines.length; line += 2) {\n var item = null;\n if ((parts = lineRE1.exec(lines[line]))) {\n item = {\n 'url': parts[2],\n 'func': parts[3],\n 'args': [],\n 'line': +parts[1],\n 'column': null\n };\n } else if ((parts = lineRE2.exec(lines[line]))) {\n item = {\n 'url': parts[3],\n 'func': parts[4],\n 'args': [],\n 'line': +parts[1],\n 'column': null // TODO: Check to see if inline#1 (+parts[2]) points to the script number or column number.\n };\n var relativeLine = (+parts[1]); // relative to the start of the