1
0

client.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. 'use strict';
  2. var stringify = require('../vendor/json-stringify-safe');
  3. var parsers = require('./parsers');
  4. var zlib = require('zlib');
  5. var utils = require('./utils');
  6. var uuid = require('uuid');
  7. var transports = require('./transports');
  8. var nodeUtil = require('util'); // nodeUtil to avoid confusion with "utils"
  9. var events = require('events');
  10. var domain = require('domain');
  11. var md5 = require('md5');
  12. var instrumentor = require('./instrumentation/instrumentor');
  13. var extend = utils.extend;
  14. function Raven() {
  15. this.breadcrumbs = {
  16. record: this.captureBreadcrumb.bind(this)
  17. };
  18. }
  19. nodeUtil.inherits(Raven, events.EventEmitter);
  20. extend(Raven.prototype, {
  21. config: function config(dsn, options) {
  22. // We get lots of users using raven-node when they want raven-js, hence this warning if it seems like a browser
  23. if (
  24. typeof window !== 'undefined' &&
  25. typeof document !== 'undefined' &&
  26. typeof navigator !== 'undefined'
  27. ) {
  28. utils.consoleAlertOnce(
  29. "This looks like a browser environment; are you sure you don't want Raven.js for browser JavaScript? https://sentry.io/for/javascript"
  30. );
  31. }
  32. if (arguments.length === 0) {
  33. // no arguments, use default from environment
  34. dsn = global.process.env.SENTRY_DSN;
  35. options = {};
  36. }
  37. if (typeof dsn === 'object') {
  38. // They must only be passing through options
  39. options = dsn;
  40. dsn = global.process.env.SENTRY_DSN;
  41. }
  42. options = options || {};
  43. this.raw_dsn = dsn;
  44. this.dsn = utils.parseDSN(dsn);
  45. this.name =
  46. options.name || global.process.env.SENTRY_NAME || require('os').hostname();
  47. this.root = options.root || global.process.cwd();
  48. this.transport = options.transport || transports[this.dsn.protocol];
  49. this.sendTimeout = options.sendTimeout || 1;
  50. this.release = options.release || global.process.env.SENTRY_RELEASE;
  51. this.environment =
  52. options.environment ||
  53. global.process.env.SENTRY_ENVIRONMENT ||
  54. global.process.env.NODE_ENV;
  55. // autoBreadcrumbs: true enables all, autoBreadcrumbs: false disables all
  56. // autoBreadcrumbs: { http: true } enables a single type
  57. this.autoBreadcrumbs = options.autoBreadcrumbs || false;
  58. // default to 30, don't allow higher than 100
  59. this.maxBreadcrumbs = Math.max(0, Math.min(options.maxBreadcrumbs || 30, 100));
  60. this.captureUnhandledRejections = options.captureUnhandledRejections;
  61. this.loggerName = options.logger;
  62. this.dataCallback = options.dataCallback;
  63. this.shouldSendCallback = options.shouldSendCallback;
  64. this.sampleRate = typeof options.sampleRate === 'undefined' ? 1 : options.sampleRate;
  65. this.maxReqQueueCount = options.maxReqQueueCount || 100;
  66. this.parseUser = options.parseUser;
  67. this.stacktrace = options.stacktrace || false;
  68. if (!this.dsn) {
  69. utils.consoleAlert('no DSN provided, error reporting disabled');
  70. }
  71. if (this.dsn.protocol === 'https') {
  72. // In case we want to provide our own SSL certificates / keys
  73. this.ca = options.ca || null;
  74. }
  75. // enabled if a dsn is set
  76. this._enabled = !!this.dsn;
  77. var globalContext = (this._globalContext = {});
  78. if (options.tags) {
  79. globalContext.tags = options.tags;
  80. }
  81. if (options.extra) {
  82. globalContext.extra = options.extra;
  83. }
  84. this.onFatalError = this.defaultOnFatalError = function(err, sendErr, eventId) {
  85. console.error(err && err.stack ? err.stack : err);
  86. global.process.exit(1);
  87. };
  88. this.uncaughtErrorHandler = this.makeErrorHandler();
  89. this.on('error', function(err) {
  90. utils.consoleAlert('failed to send exception to sentry: ' + err.message);
  91. });
  92. return this;
  93. },
  94. install: function install(cb) {
  95. if (this.installed) return this;
  96. if (typeof cb === 'function') {
  97. this.onFatalError = cb;
  98. }
  99. global.process.on('uncaughtException', this.uncaughtErrorHandler);
  100. if (this.captureUnhandledRejections) {
  101. var self = this;
  102. global.process.on('unhandledRejection', function(reason, promise) {
  103. var context = (promise.domain && promise.domain.sentryContext) || {};
  104. context.extra = context.extra || {};
  105. context.extra.unhandledPromiseRejection = true;
  106. self.captureException(reason, context, function(sendErr, eventId) {
  107. if (!sendErr) {
  108. var reasonMessage = (reason && reason.message) || reason;
  109. utils.consoleAlert(
  110. 'unhandledRejection captured\n' +
  111. 'Event ID: ' +
  112. eventId +
  113. '\n' +
  114. 'Reason: ' +
  115. reasonMessage
  116. );
  117. }
  118. });
  119. });
  120. }
  121. instrumentor.instrument(this, this.autoBreadcrumbs);
  122. this.installed = true;
  123. return this;
  124. },
  125. uninstall: function uninstall() {
  126. if (!this.installed) return this;
  127. instrumentor.deinstrument(this);
  128. // todo: this works for tests for now, but isn't what we ultimately want to be doing
  129. global.process.removeAllListeners('uncaughtException');
  130. global.process.removeAllListeners('unhandledRejection');
  131. this.installed = false;
  132. return this;
  133. },
  134. makeErrorHandler: function() {
  135. var self = this;
  136. var caughtFirstError = false;
  137. var caughtSecondError = false;
  138. var calledFatalError = false;
  139. var firstError;
  140. return function(err) {
  141. if (!caughtFirstError) {
  142. // this is the first uncaught error and the ultimate reason for shutting down
  143. // we want to do absolutely everything possible to ensure it gets captured
  144. // also we want to make sure we don't go recursion crazy if more errors happen after this one
  145. firstError = err;
  146. caughtFirstError = true;
  147. self.captureException(err, {level: 'fatal'}, function(sendErr, eventId) {
  148. if (!calledFatalError) {
  149. calledFatalError = true;
  150. self.onFatalError(err, sendErr, eventId);
  151. }
  152. });
  153. } else if (calledFatalError) {
  154. // we hit an error *after* calling onFatalError - pretty boned at this point, just shut it down
  155. utils.consoleAlert(
  156. 'uncaught exception after calling fatal error shutdown callback - this is bad! forcing shutdown'
  157. );
  158. self.defaultOnFatalError(err);
  159. } else if (!caughtSecondError) {
  160. // two cases for how we can hit this branch:
  161. // - capturing of first error blew up and we just caught the exception from that
  162. // - quit trying to capture, proceed with shutdown
  163. // - a second independent error happened while waiting for first error to capture
  164. // - want to avoid causing premature shutdown before first error capture finishes
  165. // it's hard to immediately tell case 1 from case 2 without doing some fancy/questionable domain stuff
  166. // so let's instead just delay a bit before we proceed with our action here
  167. // in case 1, we just wait a bit unnecessarily but ultimately do the same thing
  168. // in case 2, the delay hopefully made us wait long enough for the capture to finish
  169. // two potential nonideal outcomes:
  170. // nonideal case 1: capturing fails fast, we sit around for a few seconds unnecessarily before proceeding correctly by calling onFatalError
  171. // nonideal case 2: case 2 happens, 1st error is captured but slowly, timeout completes before capture and we treat second error as the sendErr of (nonexistent) failure from trying to capture first error
  172. // note that after hitting this branch, we might catch more errors where (caughtSecondError && !calledFatalError)
  173. // we ignore them - they don't matter to us, we're just waiting for the second error timeout to finish
  174. caughtSecondError = true;
  175. setTimeout(function() {
  176. if (!calledFatalError) {
  177. // it was probably case 1, let's treat err as the sendErr and call onFatalError
  178. calledFatalError = true;
  179. self.onFatalError(firstError, err);
  180. } else {
  181. // it was probably case 2, our first error finished capturing while we waited, cool, do nothing
  182. }
  183. }, (self.sendTimeout + 1) * 1000); // capturing could take at least sendTimeout to fail, plus an arbitrary second for how long it takes to collect surrounding source etc
  184. }
  185. };
  186. },
  187. generateEventId: function generateEventId() {
  188. return uuid().replace(/-/g, '');
  189. },
  190. process: function process(eventId, kwargs, cb) {
  191. // prod codepaths shouldn't hit this branch, for testing
  192. if (typeof eventId === 'object') {
  193. cb = kwargs;
  194. kwargs = eventId;
  195. eventId = this.generateEventId();
  196. }
  197. var domainContext = (domain.active && domain.active.sentryContext) || {};
  198. var globalContext = this._globalContext || {};
  199. kwargs.user = extend({}, globalContext.user, domainContext.user, kwargs.user);
  200. kwargs.tags = extend({}, globalContext.tags, domainContext.tags, kwargs.tags);
  201. kwargs.extra = extend({}, globalContext.extra, domainContext.extra, kwargs.extra);
  202. // Perform a shallow copy of breadcrums to not send one that we'll capture below through as well
  203. kwargs.breadcrumbs = {
  204. values:
  205. (domainContext.breadcrumbs && domainContext.breadcrumbs.slice()) ||
  206. (globalContext.breadcrumbs && globalContext.breadcrumbs.slice()) ||
  207. []
  208. };
  209. /*
  210. `request` is our specified property name for the http interface: https://docs.sentry.io/clientdev/interfaces/http/
  211. `req` is the conventional name for a request object in node/express/etc
  212. we want to enable someone to pass a `request` property to kwargs according to http interface
  213. but also want to provide convenience for passing a req object and having us parse it out
  214. so we only parse a `req` property if the `request` property is absent/empty (and hence we won't clobber)
  215. parseUser returns a partial kwargs object with a `request` property and possibly a `user` property
  216. */
  217. var request = this._createRequestObject(
  218. globalContext.request,
  219. domainContext.request,
  220. kwargs.request
  221. );
  222. delete kwargs.request;
  223. if (Object.keys(request).length === 0) {
  224. request = this._createRequestObject(
  225. globalContext.req,
  226. domainContext.req,
  227. kwargs.req
  228. );
  229. delete kwargs.req;
  230. }
  231. if (Object.keys(request).length > 0) {
  232. var parseUser = Object.keys(kwargs.user).length === 0 ? this.parseUser : false;
  233. extend(kwargs, parsers.parseRequest(request, parseUser));
  234. } else {
  235. kwargs.request = {};
  236. }
  237. kwargs.modules = utils.getModules();
  238. kwargs.server_name = kwargs.server_name || this.name;
  239. if (typeof global.process.version !== 'undefined') {
  240. kwargs.extra.node = global.process.version;
  241. }
  242. kwargs.environment = kwargs.environment || this.environment;
  243. kwargs.logger = kwargs.logger || this.loggerName;
  244. kwargs.event_id = eventId;
  245. kwargs.timestamp = new Date().toISOString().split('.')[0];
  246. kwargs.project = this.dsn && this.dsn.project_id;
  247. kwargs.platform = 'node';
  248. kwargs.release = this.release;
  249. // Cleanup empty properties before sending them to the server
  250. Object.keys(kwargs).forEach(function(key) {
  251. if (kwargs[key] == null || kwargs[key] === '') {
  252. delete kwargs[key];
  253. }
  254. });
  255. if (this.dataCallback) {
  256. kwargs = this.dataCallback(kwargs);
  257. }
  258. // Capture breadcrumb before sending it, as we also want to have it even when
  259. // it was dropped due to sampleRate or shouldSendCallback
  260. this.captureBreadcrumb({
  261. category: 'sentry',
  262. message: kwargs.message,
  263. event_id: kwargs.event_id,
  264. level: kwargs.level || 'error' // presume error unless specified
  265. });
  266. var shouldSend = true;
  267. if (!this._enabled) shouldSend = false;
  268. if (this.shouldSendCallback && !this.shouldSendCallback(kwargs)) shouldSend = false;
  269. if (Math.random() >= this.sampleRate) shouldSend = false;
  270. if (shouldSend) {
  271. this.send(kwargs, cb);
  272. } else {
  273. // wish there was a good way to communicate to cb why we didn't send; worth considering cb api change?
  274. // could be shouldSendCallback, could be disabled, could be sample rate
  275. // avoiding setImmediate here because node 0.8
  276. cb &&
  277. setTimeout(function() {
  278. cb(null, eventId);
  279. }, 0);
  280. }
  281. },
  282. send: function send(kwargs, cb) {
  283. var self = this;
  284. var skwargs = stringify(kwargs);
  285. var eventId = kwargs.event_id;
  286. zlib.deflate(skwargs, function(err, buff) {
  287. var message = buff.toString('base64'),
  288. timestamp = new Date().getTime(),
  289. headers = {
  290. 'X-Sentry-Auth': utils.getAuthHeader(
  291. timestamp,
  292. self.dsn.public_key,
  293. self.dsn.private_key
  294. ),
  295. 'Content-Type': 'application/octet-stream',
  296. 'Content-Length': message.length
  297. };
  298. self.transport.send(self, message, headers, eventId, cb);
  299. });
  300. },
  301. captureMessage: function captureMessage(message, kwargs, cb) {
  302. if (!cb && typeof kwargs === 'function') {
  303. cb = kwargs;
  304. kwargs = {};
  305. } else {
  306. kwargs = utils.isPlainObject(kwargs) ? extend({}, kwargs) : {};
  307. }
  308. var eventId = this.generateEventId();
  309. if (this.stacktrace) {
  310. var ex = new Error(message);
  311. console.log(ex);
  312. utils.parseStack(
  313. ex,
  314. function(frames) {
  315. // We trim last frame, as it's our `new Error(message)` statement itself, which is redundant
  316. kwargs.stacktrace = {
  317. frames: frames.slice(0, -1)
  318. };
  319. this.process(eventId, parsers.parseText(message, kwargs), cb);
  320. }.bind(this)
  321. );
  322. } else {
  323. this.process(eventId, parsers.parseText(message, kwargs), cb);
  324. }
  325. return eventId;
  326. },
  327. captureException: function captureException(err, kwargs, cb) {
  328. if (!cb && typeof kwargs === 'function') {
  329. cb = kwargs;
  330. kwargs = {};
  331. } else {
  332. kwargs = utils.isPlainObject(kwargs) ? extend({}, kwargs) : {};
  333. }
  334. if (!utils.isError(err)) {
  335. if (utils.isPlainObject(err)) {
  336. // This will allow us to group events based on top-level keys
  337. // which is much better than creating new group when any key/value change
  338. var keys = Object.keys(err).sort();
  339. var message =
  340. 'Non-Error exception captured with keys: ' +
  341. utils.serializeKeysForMessage(keys);
  342. kwargs = extend(kwargs, {
  343. message: message,
  344. fingerprint: [md5(keys)],
  345. extra: kwargs.extra || {}
  346. });
  347. kwargs.extra.__serialized__ = utils.serializeException(err);
  348. err = new Error(message);
  349. } else {
  350. // This handles when someone does:
  351. // throw "something awesome";
  352. // We synthesize an Error here so we can extract a (rough) stack trace.
  353. err = new Error(err);
  354. }
  355. }
  356. var self = this;
  357. var eventId = this.generateEventId();
  358. parsers.parseError(err, kwargs, function(kw) {
  359. self.process(eventId, kw, cb);
  360. });
  361. return eventId;
  362. },
  363. context: function(ctx, func) {
  364. if (!func && typeof ctx === 'function') {
  365. func = ctx;
  366. ctx = {};
  367. }
  368. // todo/note: raven-js takes an args param to do apply(this, args)
  369. // i don't think it's correct/necessary to bind this to the wrap call
  370. // and i don't know if we need to support the args param; it's undocumented
  371. return this.wrap(ctx, func).apply(null);
  372. },
  373. wrap: function(options, func) {
  374. if (!this.installed) {
  375. utils.consoleAlertOnce(
  376. 'Raven has not been installed, therefore no breadcrumbs will be captured. Call `Raven.config(...).install()` to fix this.'
  377. );
  378. }
  379. if (!func && typeof options === 'function') {
  380. func = options;
  381. options = {};
  382. }
  383. var wrapDomain = domain.create();
  384. // todo: better property name than sentryContext, maybe __raven__ or sth?
  385. wrapDomain.sentryContext = options;
  386. wrapDomain.on('error', this.uncaughtErrorHandler);
  387. var wrapped = wrapDomain.bind(func);
  388. for (var property in func) {
  389. if ({}.hasOwnProperty.call(func, property)) {
  390. wrapped[property] = func[property];
  391. }
  392. }
  393. wrapped.prototype = func.prototype;
  394. wrapped.__raven__ = true;
  395. wrapped.__inner__ = func;
  396. // note: domain.bind sets wrapped.domain, but it's not documented, unsure if we should rely on that
  397. wrapped.__domain__ = wrapDomain;
  398. return wrapped;
  399. },
  400. interceptErr: function(options, func) {
  401. if (!func && typeof options === 'function') {
  402. func = options;
  403. options = {};
  404. }
  405. var self = this;
  406. var wrapped = function() {
  407. var err = arguments[0];
  408. if (utils.isError(err)) {
  409. self.captureException(err, options);
  410. } else {
  411. func.apply(null, arguments);
  412. }
  413. };
  414. // repetitive with wrap
  415. for (var property in func) {
  416. if ({}.hasOwnProperty.call(func, property)) {
  417. wrapped[property] = func[property];
  418. }
  419. }
  420. wrapped.prototype = func.prototype;
  421. wrapped.__raven__ = true;
  422. wrapped.__inner__ = func;
  423. return wrapped;
  424. },
  425. setContext: function setContext(ctx) {
  426. if (domain.active) {
  427. domain.active.sentryContext = ctx;
  428. } else {
  429. this._globalContext = ctx;
  430. }
  431. return this;
  432. },
  433. mergeContext: function mergeContext(ctx) {
  434. extend(this.getContext(), ctx);
  435. return this;
  436. },
  437. getContext: function getContext() {
  438. if (domain.active) {
  439. if (!domain.active.sentryContext) {
  440. domain.active.sentryContext = {};
  441. utils.consoleAlert('sentry context not found on active domain');
  442. }
  443. return domain.active.sentryContext;
  444. }
  445. return this._globalContext;
  446. },
  447. setCallbackHelper: function(propertyName, callback) {
  448. var original = this[propertyName];
  449. if (typeof callback === 'function') {
  450. this[propertyName] = function(data) {
  451. return callback(data, original);
  452. };
  453. } else {
  454. this[propertyName] = callback;
  455. }
  456. return this;
  457. },
  458. /*
  459. * Set the dataCallback option
  460. *
  461. * @param {function} callback The callback to run which allows the
  462. * data blob to be mutated before sending
  463. * @return {Raven}
  464. */
  465. setDataCallback: function(callback) {
  466. return this.setCallbackHelper('dataCallback', callback);
  467. },
  468. /*
  469. * Set the shouldSendCallback option
  470. *
  471. * @param {function} callback The callback to run which allows
  472. * introspecting the blob before sending
  473. * @return {Raven}
  474. */
  475. setShouldSendCallback: function(callback) {
  476. return this.setCallbackHelper('shouldSendCallback', callback);
  477. },
  478. requestHandler: function() {
  479. var self = this;
  480. return function ravenRequestMiddleware(req, res, next) {
  481. self.context({req: req}, function() {
  482. domain.active.add(req);
  483. domain.active.add(res);
  484. next();
  485. });
  486. };
  487. },
  488. errorHandler: function() {
  489. var self = this;
  490. return function ravenErrorMiddleware(err, req, res, next) {
  491. var status =
  492. err.status ||
  493. err.statusCode ||
  494. err.status_code ||
  495. (err.output && err.output.statusCode) ||
  496. 500;
  497. // skip anything not marked as an internal server error
  498. if (status < 500) return next(err);
  499. var eventId = self.captureException(err, {req: req});
  500. res.sentry = eventId;
  501. return next(err);
  502. };
  503. },
  504. captureBreadcrumb: function(breadcrumb) {
  505. // Avoid capturing global-scoped breadcrumbs before instrumentation finishes
  506. if (!this.installed) return;
  507. breadcrumb = extend(
  508. {
  509. timestamp: +new Date() / 1000
  510. },
  511. breadcrumb
  512. );
  513. var currCtx = this.getContext();
  514. if (!currCtx.breadcrumbs) currCtx.breadcrumbs = [];
  515. currCtx.breadcrumbs.push(breadcrumb);
  516. if (currCtx.breadcrumbs.length > this.maxBreadcrumbs) {
  517. currCtx.breadcrumbs.shift();
  518. }
  519. this.setContext(currCtx);
  520. },
  521. _createRequestObject: function() {
  522. /**
  523. * When using proxy, some of the attributes of req/request objects are non-enumerable.
  524. * To make sure, that they are still available to us after we consolidate our sources
  525. * (eg. globalContext.request + domainContext.request + kwargs.request),
  526. * we manually pull them out from original objects.
  527. *
  528. * Same scenario happens when some frameworks (eg. Koa) decide to use request within
  529. * request. eg `this.request.req`, which adds aliases to the main `request` object.
  530. * By manually reassigning them here, we don't need to add additional checks
  531. * like `req.method || (req.req && req.req.method)`
  532. *
  533. * We don't use Object.assign/extend as it's only merging over objects own properties,
  534. * and we don't want to go through all of the properties as well, as we simply don't
  535. * need all of them.
  536. **/
  537. var sources = Array.from(arguments).filter(function(source) {
  538. return Object.prototype.toString.call(source) === '[object Object]';
  539. });
  540. sources = [{}].concat(sources);
  541. var request = extend.apply(null, sources);
  542. var nonEnumerables = [
  543. 'headers',
  544. 'hostname',
  545. 'ip',
  546. 'method',
  547. 'protocol',
  548. 'query',
  549. 'secure',
  550. 'url'
  551. ];
  552. nonEnumerables.forEach(function(key) {
  553. sources.forEach(function(source) {
  554. if (source[key]) request[key] = source[key];
  555. });
  556. });
  557. /**
  558. * Check for 'host' *only* after we checked for 'hostname' first.
  559. * This way we can avoid the noise coming from Express deprecation warning
  560. * https://github.com/expressjs/express/blob/b97faff6e2aa4d34d79485fe4331cb0eec13ad57/lib/request.js#L450-L452
  561. * REF: https://github.com/getsentry/raven-node/issues/96#issuecomment-354748884
  562. **/
  563. if (!request.hasOwnProperty('hostname')) {
  564. sources.forEach(function(source) {
  565. if (source.host) request.host = source.host;
  566. });
  567. }
  568. return request;
  569. }
  570. });
  571. // Maintain old API compat, need to make sure arguments length is preserved
  572. function Client(dsn, options) {
  573. if (dsn instanceof Client) return dsn;
  574. var ravenInstance = new Raven();
  575. return ravenInstance.config.apply(ravenInstance, arguments);
  576. }
  577. nodeUtil.inherits(Client, Raven);
  578. // Singleton-by-default but not strictly enforced
  579. // todo these extra export props are sort of an adhoc mess, better way to manage?
  580. var defaultInstance = new Raven();
  581. defaultInstance.Client = Client;
  582. defaultInstance.version = require('../package.json').version;
  583. defaultInstance.disableConsoleAlerts = utils.disableConsoleAlerts;
  584. module.exports = defaultInstance;