wat_catcher 0.10.4 → 0.10.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b4b87ef52f2931cf2b190b85a0fc787f7be8f1de
4
- data.tar.gz: 0d11439d06ac221a9898db995360ecbd293366ca
3
+ metadata.gz: dcc9644bbfc8ea819edbca86fb781148929bf4f2
4
+ data.tar.gz: 9c472e6b2d257386ddd08d5a5f33935b80a9dfe3
5
5
  SHA512:
6
- metadata.gz: 10a748f45dc6d90becdd7d610b42dc8647b33290cf0a51def01087237250327c6cce07714e0748f281404f33407f163dc52fbbed87686b50baa1b38b9ef87ffe
7
- data.tar.gz: 2eb6e7ccbd403ed9a6160c40e82ea4e19df1a021691f7e1fd6fa3d55e7c7b55dbc45d670f61e3ea59016685963d8602b391fad00b59802552fcba9477384f8f4
6
+ metadata.gz: b4e6f441e561eecd007a38e0ac1f4e3f1beb6ff35b73688d92c7a1e7b1e66417700c39088f4a526753cbbab28620baeb546ff2a0ca5122ec96a36e18d7e0a979
7
+ data.tar.gz: 55a12a13df94364a4edb916aa4579bf0909210b4716c8a91d4571922fad85da0e0675480d63327efe733e10d5a91e5977662c825486d798af68d516e2b9ed515
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- wat_catcher (0.10.4)
4
+ wat_catcher (0.10.5)
5
5
  coffee-rails
6
6
  httpclient
7
7
  sidekiq
@@ -5,7 +5,7 @@ module WatCatcher
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- around_filter :catch_wats
8
+ around_action :catch_wats
9
9
 
10
10
  helper_method :wat_user
11
11
  end
@@ -15,11 +15,11 @@ module WatCatcher
15
15
  end
16
16
 
17
17
  def disable_wat_report
18
- env["wat_report_disabled"] = true
18
+ request.env["wat_report_disabled"] = true
19
19
  end
20
20
 
21
21
  def report_wat?
22
- !!(env["wat_report"].present? && !env["wat_report_disabled"])
22
+ !!(request.env["wat_report"].present? && !request.env["wat_report_disabled"])
23
23
  end
24
24
 
25
25
  def catch_wats(&block)
@@ -29,7 +29,7 @@ module WatCatcher
29
29
  begin
30
30
  user = wat_user
31
31
  rescue;end
32
- env["wat_report"] = {
32
+ request.env["wat_report"] = {
33
33
  request: request,
34
34
  user: user
35
35
  }
@@ -1,3 +1,3 @@
1
1
  module WatCatcher
2
- VERSION = "0.10.4"
2
+ VERSION = "0.10.5"
3
3
  end
@@ -1,4 +1,4 @@
1
- //
1
+ // Last updated: 8/2/16
2
2
  // **Bugsnag.js** is the official JavaScript notifier for
3
3
  // [Bugsnag](https://bugsnag.com).
4
4
  //
@@ -10,28 +10,51 @@
10
10
  //
11
11
 
12
12
  // The `Bugsnag` object is the only globally exported variable
13
- (function(definition) {
14
- var old = window.Bugsnag;
15
- window.Bugsnag = definition(window, document, navigator, old);
16
- })(function (window, document, navigator, old) {
13
+ (function (window, old) {
17
14
  var self = {},
18
- lastEvent,
19
- lastScript,
20
- previousNotification,
21
- shouldCatch = true,
22
- ignoreOnError = 0,
23
-
24
- // We've seen cases where individual clients can infinite loop sending us errors
25
- // (in some cases 10,000+ errors per page). This limit is at the point where
26
- // you've probably learned everything useful there is to debug the problem,
27
- // and we're happy to under-estimate the count to save the client (and Bugsnag's) resources.
28
- eventsRemaining = 10;
29
-
15
+ lastScript,
16
+ previousNotification,
17
+ shouldCatch = true,
18
+ ignoreOnError = 0,
19
+ breadcrumbs = [],
20
+
21
+ // We've seen cases where individual clients can infinite loop sending us errors
22
+ // (in some cases 10,000+ errors per page). This limit is at the point where
23
+ // you've probably learned everything useful there is to debug the problem,
24
+ // and we're happy to under-estimate the count to save the client (and Bugsnag's) resources.
25
+ eventsRemaining = 10,
26
+ // The default depth of attached metadata which is parsed before truncation. It
27
+ // is configurable via the `maxDepth` setting.
28
+ maxPayloadDepth = 5;
29
+
30
+ // #### Bugsnag.noConflict
31
+ //
32
+ // This is obsolete with UMD, as we cannot assume the global scope is polluted with
33
+ // the Bugsnag object anyway. In this case, it's up to the host Javascript file to
34
+ // correctly utilise this functionality.
35
+ //
36
+ // Maybe it's worth removing all together, if we're loading via any UMD method.
30
37
  self.noConflict = function() {
31
38
  window.Bugsnag = old;
39
+ if (typeof old === "undefined") {
40
+ delete window.Bugsnag;
41
+ }
32
42
  return self;
33
43
  };
34
44
 
45
+ // ### Bugsnag.refresh
46
+ //
47
+ // Resets the Bugsnag rate limit. If you have a large single-page app, you may
48
+ // wish to call this in your router to avoid exception reports being thrown
49
+ // away.
50
+ //
51
+ // By default Bugsnag aggressively limits the number of exception reports from
52
+ // one page load. This protects both the client's browser and our servers in
53
+ // cases where exceptions are thrown in tight loops or scroll handlers.
54
+ self.refresh = function() {
55
+ eventsRemaining = 10;
56
+ };
57
+
35
58
  //
36
59
  // ### Manual error notification (public methods)
37
60
  //
@@ -48,13 +71,16 @@
48
71
  // Since most JavaScript exceptions use the `Error` class, we also allow
49
72
  // you to provide a custom error name when calling `notifyException`.
50
73
  //
51
- // You should never pass severity. It's there for internal use, so we
52
- // can mark uncaught exceptions as "fatal". The default value is "error"
53
- // and "warning" is also supported by the backend, all other values cause
54
- // the notification to be dropped; and you will not see it in your dashboard.
74
+ // The default value is "warning" and "error" and "info" are also supported by the
75
+ // backend, all other values cause the notification to be dropped; and you
76
+ // will not see it in your dashboard.
55
77
  self.notifyException = function (exception, name, metaData, severity) {
78
+ if (!exception) {
79
+ return;
80
+ }
56
81
  if (name && typeof name !== "string") {
57
82
  metaData = name;
83
+ name = undefined;
58
84
  }
59
85
  if (!metaData) {
60
86
  metaData = {};
@@ -68,7 +94,7 @@
68
94
  file: exception.fileName || exception.sourceURL,
69
95
  lineNumber: exception.lineNumber || exception.line,
70
96
  columnNumber: exception.columnNumber ? exception.columnNumber + 1 : undefined,
71
- severity: severity || "error"
97
+ severity: severity || "warning"
72
98
  }, metaData);
73
99
  };
74
100
 
@@ -76,15 +102,74 @@
76
102
  //
77
103
  // Notify Bugsnag about an error by passing in a `name` and `message`,
78
104
  // without requiring an exception.
79
- self.notify = function (name, message, metaData) {
105
+ self.notify = function (name, message, metaData, severity) {
80
106
  sendToBugsnag({
81
107
  name: name,
82
108
  message: message,
83
109
  stacktrace: generateStacktrace(),
84
- severity: "error"
110
+ // These are defaults so that 'bugsnag.notify()' calls show up in old IE,
111
+ // newer browsers get a legit stacktrace from generateStacktrace().
112
+ file: window.location.toString(),
113
+ lineNumber: 1,
114
+ severity: severity || "warning"
85
115
  }, metaData);
86
116
  };
87
117
 
118
+ // #### Bugsnag.leaveBreadcrumb(value, [metaData])
119
+ //
120
+ // Add a breadcrumb to the array of breadcrumbs to be sent to Bugsnag when the next exception occurs
121
+ // - `value` (string|object) If this is an object, it will be used as the entire breadcrumb object
122
+ // and any missing `type`, `name` or `timestamp` fields will get default values.
123
+ // if this is a string and metaData is provided, then `value` will be used as the `name` of the
124
+ // breadcrumb.
125
+ // if `value` is a string and is the only argument the breadcrumb will have `manual` type and
126
+ // the value will be used as the `message` field of `metaData`.
127
+ //
128
+ // - `metadata` (optional, object) - Additional information about the breadcrumb. Values limited to 140 characters.
129
+ self.leaveBreadcrumb = function(value, metaData) {
130
+ // default crumb
131
+ var crumb = {
132
+ type: "manual",
133
+ name: "Manual",
134
+ timestamp: new Date().getTime()
135
+ };
136
+
137
+ switch (typeof value) {
138
+ case "object":
139
+ crumb = merge(crumb, value);
140
+ break;
141
+ case "string":
142
+ if (metaData && typeof metaData === "object") {
143
+ crumb = merge(crumb, {
144
+ name: value,
145
+ metaData: metaData
146
+ });
147
+ } else {
148
+ crumb.metaData = { message: value };
149
+ }
150
+ break;
151
+ default:
152
+ log("expecting 1st argument to leaveBreadcrumb to be a 'string' or 'object', got " + typeof value);
153
+ return;
154
+ }
155
+
156
+ var lastCrumb = breadcrumbs.slice(-1)[0];
157
+ if (breadcrumbsAreEqual(crumb, lastCrumb)) {
158
+ lastCrumb.count = lastCrumb.count || 1;
159
+ lastCrumb.count++;
160
+ } else {
161
+ crumb.name = truncate(crumb.name, 32);
162
+ breadcrumbs.push(truncateDeep(crumb, 140));
163
+ }
164
+ };
165
+
166
+ function breadcrumbsAreEqual(crumb1, crumb2) {
167
+ return crumb1 && crumb2 &&
168
+ crumb1.type === crumb2.type &&
169
+ crumb1.name === crumb2.name &&
170
+ isEqual(crumb1.metaData, crumb2.metaData);
171
+ }
172
+
88
173
  // Return a function acts like the given function, but reports
89
174
  // any exceptions to Bugsnag before re-throwing them.
90
175
  //
@@ -94,17 +179,14 @@
94
179
  // If you call wrap twice on the same function, it'll give you back the
95
180
  // same wrapped function. This lets removeEventListener to continue to
96
181
  // work.
97
- function wrap(_super, options) {
182
+ function wrap(_super) {
98
183
  try {
99
184
  if (typeof _super !== "function") {
100
185
  return _super;
101
186
  }
102
187
  if (!_super.bugsnag) {
103
188
  var currentScript = getCurrentScript();
104
- _super.bugsnag = function (event) {
105
- if (options && options.eventHandler) {
106
- lastEvent = event;
107
- }
189
+ _super.bugsnag = function () {
108
190
  lastScript = currentScript;
109
191
 
110
192
  // We set shouldCatch to false on IE < 10 because catching the error ruins the file/line as reported in window.onerror,
@@ -114,10 +196,8 @@
114
196
  try {
115
197
  return _super.apply(this, arguments);
116
198
  } catch (e) {
117
- // We do this rather than stashing treating the error like lastEvent
118
- // because in FF 26 onerror is not called for synthesized event handlers.
119
199
  if (getSetting("autoNotify", true)) {
120
- self.notifyException(e, null, null, "fatal");
200
+ self.notifyException(e, null, null, "error");
121
201
  ignoreNextOnError();
122
202
  }
123
203
  throw e;
@@ -135,13 +215,185 @@
135
215
  }
136
216
  return _super.bugsnag;
137
217
 
138
- // This can happen if _super is not a normal javascript function.
139
- // For example, see https://github.com/bugsnag/bugsnag-js/issues/28
218
+ // This can happen if _super is not a normal javascript function.
219
+ // For example, see https://github.com/bugsnag/bugsnag-js/issues/28
140
220
  } catch (e) {
141
221
  return _super;
142
222
  }
143
223
  }
144
224
 
225
+ var _hasAddEventListener = (typeof window.addEventListener !== "undefined");
226
+
227
+ // Setup breadcrumbs for click events
228
+ function trackClicks() {
229
+ if(!getBreadcrumbSetting("autoBreadcrumbsClicks", true)) {
230
+ return;
231
+ }
232
+
233
+ if (!_hasAddEventListener) {
234
+ return;
235
+ }
236
+
237
+ var callback = function(event) {
238
+ self.leaveBreadcrumb({
239
+ type: "user",
240
+ name: "UI click",
241
+ metaData: {
242
+ targetText: nodeText(event.target),
243
+ targetSelector: nodeLabel(event.target)
244
+ }
245
+ });
246
+ };
247
+
248
+ window.addEventListener("click", callback, true);
249
+ }
250
+
251
+ // Setup breadcrumbs for console.log, console.warn, console.error
252
+ function trackConsoleLog(){
253
+ if(!window.console || typeof window.console.log !== "function" || !getBreadcrumbSetting("autoBreadcrumbsConsole")) {
254
+ return;
255
+ }
256
+
257
+ function trackLog(severity, args) {
258
+ self.leaveBreadcrumb({
259
+ type: "log",
260
+ name: "Console output",
261
+ metaData: {
262
+ severity: severity,
263
+ message: Array.prototype.slice.call(args).join(", ")
264
+ }
265
+ });
266
+ }
267
+
268
+ enhance(console, "log", function() {
269
+ trackLog("log", arguments);
270
+ });
271
+
272
+ enhance(console, "warn", function() {
273
+ trackLog("warn", arguments);
274
+ });
275
+
276
+ enhance(console, "error", function() {
277
+ trackLog("error", arguments);
278
+ });
279
+ }
280
+
281
+ // Setup breadcrumbs for history navigation events
282
+ function trackNavigation() {
283
+ if(!getBreadcrumbSetting("autoBreadcrumbsNavigation")) {
284
+ return;
285
+ }
286
+
287
+ // check for browser support
288
+ if (!_hasAddEventListener ||
289
+ !window.history ||
290
+ !window.history.state ||
291
+ !window.history.pushState ||
292
+ !window.history.pushState.bind
293
+ ) {
294
+ return;
295
+ }
296
+
297
+ function parseHash(url) {
298
+ return url.split("#")[1] || "";
299
+ }
300
+
301
+ function buildHashChange(event) {
302
+ var oldURL = event.oldURL,
303
+ newURL = event.newURL,
304
+ metaData = {};
305
+
306
+ // not supported in old browsers
307
+ if (oldURL && newURL) {
308
+ metaData.from = parseHash(oldURL);
309
+ metaData.to = parseHash(newURL);
310
+ } else {
311
+ metaData.to = location.hash;
312
+ }
313
+
314
+ return {
315
+ type: "navigation",
316
+ name: "Hash changed",
317
+ metaData: metaData
318
+ };
319
+ }
320
+
321
+ function buildPopState() {
322
+ return {
323
+ type: "navigation",
324
+ name: "Navigated back"
325
+ };
326
+ }
327
+
328
+ function buildPageHide() {
329
+ return {
330
+ type: "navigation",
331
+ name: "Page hidden"
332
+ };
333
+ }
334
+
335
+ function buildPageShow() {
336
+ return {
337
+ type: "navigation",
338
+ name: "Page shown"
339
+ };
340
+ }
341
+
342
+ function buildLoad() {
343
+ return {
344
+ type: "navigation",
345
+ name: "Page loaded"
346
+ };
347
+ }
348
+
349
+ function buildDOMContentLoaded() {
350
+ return {
351
+ type: "navigation",
352
+ name: "DOMContentLoaded"
353
+ };
354
+ }
355
+
356
+ function buildStateChange(name, state, title, url) {
357
+ var currentPath = location.pathname + location.search + location.hash;
358
+ return {
359
+ type: "navigation",
360
+ name: "History " + name,
361
+ metaData: {
362
+ from: currentPath,
363
+ to: url || currentPath,
364
+ prevState: history.state,
365
+ nextState: state
366
+ }
367
+ };
368
+ }
369
+
370
+ function buildPushState(state, title, url) {
371
+ return buildStateChange("pushState", state, title, url);
372
+ }
373
+
374
+ function buildReplaceState(state, title, url) {
375
+ return buildStateChange("replaceState", state, title, url);
376
+ }
377
+
378
+ // functional fu to make it easier to setup event listeners
379
+ function wrapBuilder(builder) {
380
+ return function() {
381
+ self.leaveBreadcrumb(builder.apply(null, arguments));
382
+ };
383
+ }
384
+
385
+ window.addEventListener("hashchange", wrapBuilder(buildHashChange), true);
386
+ window.addEventListener("popstate", wrapBuilder(buildPopState), true);
387
+ window.addEventListener("pagehide", wrapBuilder(buildPageHide), true);
388
+ window.addEventListener("pageshow", wrapBuilder(buildPageShow), true);
389
+ window.addEventListener("load", wrapBuilder(buildLoad), true);
390
+ window.addEventListener("DOMContentLoaded", wrapBuilder(buildDOMContentLoaded), true);
391
+
392
+ // create hooks for pushstate and replaceState
393
+ enhance(history, "pushState", wrapBuilder(buildPushState));
394
+ enhance(history, "replaceState", wrapBuilder(buildReplaceState));
395
+ }
396
+
145
397
  //
146
398
  // ### Script tag tracking
147
399
  //
@@ -171,7 +423,7 @@
171
423
  var script = document.currentScript || lastScript;
172
424
 
173
425
  if (!script && synchronousScriptsRunning) {
174
- var scripts = document.getElementsByTagName("script");
426
+ var scripts = document.scripts || document.getElementsByTagName("script");
175
427
  script = scripts[scripts.length - 1];
176
428
  }
177
429
 
@@ -200,43 +452,197 @@
200
452
  // Set up default notifier settings.
201
453
  var DEFAULT_BASE_ENDPOINT = "https://notify.bugsnag.com/";
202
454
  var DEFAULT_NOTIFIER_ENDPOINT = DEFAULT_BASE_ENDPOINT + "js";
203
- var NOTIFIER_VERSION = "2.3.2";
455
+ var NOTIFIER_VERSION = "3.0.0-rc.1";
204
456
 
205
457
  // Keep a reference to the currently executing script in the DOM.
206
458
  // We'll use this later to extract settings from attributes.
207
459
  var scripts = document.getElementsByTagName("script");
208
460
  var thisScript = scripts[scripts.length - 1];
209
461
 
462
+ // Replace existing function on object with custom one, but still call the original afterwards
463
+ // example:
464
+ // enhance(console, 'log', function() {
465
+ // /* custom behavior */
466
+ // })
467
+ function enhance(object, property, newFunction) {
468
+ var oldFunction = object[property];
469
+ if (typeof oldFunction === "function") {
470
+ object[property] = function() {
471
+ newFunction.apply(this, arguments);
472
+ oldFunction.apply(this, arguments);
473
+ };
474
+ }
475
+ }
476
+
210
477
  // Simple logging function that wraps `console.log` if available.
211
478
  // This is useful for warning about configuration issues
212
479
  // eg. forgetting to set an API key.
213
480
  function log(msg) {
481
+ var disableLog = getSetting("disableLog");
482
+
214
483
  var console = window.console;
215
- if (console !== undefined && console.log !== undefined) {
484
+ if (console !== undefined && console.log !== undefined && !disableLog) {
216
485
  console.log("[Bugsnag] " + msg);
217
486
  }
218
487
  }
219
488
 
489
+ // Compare if two objects are equal.
490
+ function isEqual(obj1, obj2) {
491
+ return serialize(obj1) === serialize(obj2);
492
+ }
493
+
494
+ // extract text content from a element
495
+ function nodeText(el) {
496
+ var text = el.textContent || el.innerText || "";
497
+ return truncate(text.trim(), 40);
498
+ }
499
+
500
+ // Create a label from tagname, id and css class of the element
501
+ function nodeLabel(el) {
502
+ var parts = [el.tagName];
503
+
504
+ if (el.id) {
505
+ parts.push("#" + el.id);
506
+ }
507
+
508
+ if (el.className && el.className.length) {
509
+ var classString = "." + el.className.split(" ").join(".");
510
+ parts.push(classString);
511
+ }
512
+
513
+ var label = parts.join("");
514
+
515
+ if (!document.querySelectorAll || !Array.prototype.indexOf) {
516
+ // can't get much more advanced with the current browser
517
+ return label;
518
+ }
519
+
520
+ try {
521
+ if (document.querySelectorAll(label).length === 1) {
522
+ return label;
523
+ }
524
+ } catch (e) {
525
+ // sometime the query selector can be invalid, for example, if the id attribute is anumber.
526
+ // in these cases, just return the label as is.
527
+ return label;
528
+ }
529
+
530
+ // try to get a more specific selector if this one matches more than one element
531
+ if (el.parentNode.childNodes.length > 1) {
532
+ var index = Array.prototype.indexOf.call(el.parentNode.childNodes, el) + 1;
533
+ label = label + ":nth-child(" + index + ")";
534
+ }
535
+
536
+ if (document.querySelectorAll(label).length === 1) {
537
+ return label;
538
+ }
539
+
540
+ // try prepending the parent node selector
541
+ if (el.parentNode) {
542
+ return nodeLabel(el.parentNode) + " > " + label;
543
+ }
544
+
545
+ return label;
546
+ }
547
+
548
+ function truncate(value, length) {
549
+ var OMISSION = "(...)";
550
+
551
+ if (value && value.length > length) {
552
+ return value.slice(0, length - OMISSION.length) + OMISSION;
553
+ } else {
554
+ return value;
555
+ }
556
+ }
557
+
558
+ // truncate all string values in nested object
559
+ function truncateDeep(object, length) {
560
+ if (typeof object === "object") {
561
+ var newObject = {};
562
+ each(object, function(value, key){
563
+ if (value != null && value !== undefined) {
564
+ newObject[key] = truncateDeep(value, length);
565
+ }
566
+ });
567
+
568
+ return newObject;
569
+ } else if (typeof object === "string") {
570
+ return truncate(object, length);
571
+ } else if (object && Array === object.constructor) {
572
+ return map(object, function(value) { return truncateDeep(value, length); });
573
+ } else {
574
+ return object;
575
+ }
576
+ }
577
+
220
578
  // Deeply serialize an object into a query string. We use the PHP-style
221
579
  // nested object syntax, `nested[keys]=val`, to support heirachical
222
580
  // objects. Similar to jQuery's `$.param` method.
223
- function serialize(obj, prefix) {
224
- var str = [];
225
- for (var p in obj) {
226
- if (obj.hasOwnProperty(p) && p != null && obj[p] != null) {
227
- var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p];
228
- str.push(typeof v === "object" ? serialize(v, k) : encodeURIComponent(k) + "=" + encodeURIComponent(v));
581
+ function serialize(obj, prefix, depth) {
582
+ var maxDepth = getSetting("maxDepth", maxPayloadDepth);
583
+
584
+ if (depth >= maxDepth) {
585
+ return encodeURIComponent(prefix) + "=[RECURSIVE]";
586
+ }
587
+ depth = depth + 1 || 1;
588
+
589
+ try {
590
+ if (window.Node && obj instanceof window.Node) {
591
+ return encodeURIComponent(prefix) + "=" + encodeURIComponent(targetToString(obj));
592
+ }
593
+
594
+ var str = [];
595
+ for (var p in obj) {
596
+ if (obj.hasOwnProperty(p) && p != null && obj[p] != null) {
597
+ var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p];
598
+ str.push(typeof v === "object" ? serialize(v, k, depth) : encodeURIComponent(k) + "=" + encodeURIComponent(v));
599
+ }
600
+ }
601
+ return str.sort().join("&");
602
+ } catch (e) {
603
+ return encodeURIComponent(prefix) + "=" + encodeURIComponent("" + e);
604
+ }
605
+ }
606
+
607
+
608
+ // Map over an array or object
609
+ // re-implementation of lodash map
610
+ function map(source, iteratee) {
611
+ if (typeof iteratee === "undefined") {
612
+ return source;
613
+ }
614
+
615
+ var newArray = [];
616
+
617
+ each(source, newArray.push);
618
+
619
+ return newArray;
620
+ }
621
+
622
+ // Iterate over an array or object
623
+ // re-implementation of lodash each
624
+ function each(source, iteratee) {
625
+ if (typeof source === "object") {
626
+ for (var key in source) {
627
+ if (source.hasOwnProperty(key)) {
628
+ iteratee(source[key], key, source);
629
+ }
630
+ }
631
+ } else {
632
+ for (var index = 0; index < source.length; index++) {
633
+ iteratee(source[index], index, source);
229
634
  }
230
635
  }
231
- return str.join("&");
232
636
  }
233
637
 
234
638
  // Deep-merge the `source` object into the `target` object and return
235
639
  // the `target`. Properties in source that will overwrite those in target.
236
640
  // Similar to jQuery's `$.extend` method.
237
- function merge(target, source) {
641
+ function merge(target, source, depth) {
238
642
  if (source == null) {
239
643
  return target;
644
+ } else if (depth >= getSetting("maxDepth", maxPayloadDepth)) {
645
+ return "[RECURSIVE]";
240
646
  }
241
647
 
242
648
  target = target || {};
@@ -244,7 +650,7 @@
244
650
  if (source.hasOwnProperty(key)) {
245
651
  try {
246
652
  if (source[key].constructor === Object) {
247
- target[key] = merge(target[key], source[key]);
653
+ target[key] = merge(target[key], source[key], depth + 1 || 1);
248
654
  } else {
249
655
  target[key] = source[key];
250
656
  }
@@ -260,12 +666,23 @@
260
666
  // Make a HTTP request with given `url` and `params` object.
261
667
  // For maximum browser compatibility and cross-domain support, requests are
262
668
  // made by creating a temporary JavaScript `Image` object.
669
+ // Additionally the request can be done via XHR (needed for Chrome apps and extensions)
670
+ // To set the script to use XHR, you can specify data-notifyhandler attribute in the script tag
671
+ // Eg. `<script data-notifyhandler="xhr">` - the request defaults to image if attribute is not set
263
672
  function request(url, params) {
673
+ url += "?" + serialize(params) + "&ct=img&cb=" + new Date().getTime();
264
674
  if (typeof BUGSNAG_TESTING !== "undefined" && self.testRequest) {
265
675
  self.testRequest(url, params);
266
676
  } else {
267
- var img = new Image();
268
- img.src = url + "?" + serialize(params) + "&ct=img&cb=" + new Date().getTime();
677
+ var notifyHandler = getSetting("notifyHandler");
678
+ if (notifyHandler === "xhr") {
679
+ var xhr = new XMLHttpRequest();
680
+ xhr.open("GET", url, true);
681
+ xhr.send();
682
+ } else {
683
+ var img = new Image();
684
+ img.src = url;
685
+ }
269
686
  }
270
687
  }
271
688
 
@@ -276,12 +693,17 @@
276
693
  function getData(node) {
277
694
  var dataAttrs = {};
278
695
  var dataRegex = /^data\-([\w\-]+)$/;
279
- var attrs = node.attributes;
280
- for (var i = 0; i < attrs.length; i++) {
281
- var attr = attrs[i];
282
- if (dataRegex.test(attr.nodeName)) {
283
- var key = attr.nodeName.match(dataRegex)[1];
284
- dataAttrs[key] = attr.nodeValue;
696
+
697
+ // If the node doesn't exist due to being loaded as a commonjs module,
698
+ // then return an empty object and fallback to self[].
699
+ if (node) {
700
+ var attrs = node.attributes;
701
+ for (var i = 0; i < attrs.length; i++) {
702
+ var attr = attrs[i];
703
+ if (dataRegex.test(attr.nodeName)) {
704
+ var key = attr.nodeName.match(dataRegex)[1];
705
+ dataAttrs[key] = attr.value || attr.nodeValue;
706
+ }
285
707
  }
286
708
  }
287
709
 
@@ -302,7 +724,7 @@
302
724
 
303
725
  // Validate a Bugsnag API key exists and is of the correct format.
304
726
  function validateApiKey(apiKey) {
305
- if (apiKey == null || !apiKey.match(API_KEY_REGEX)) {
727
+ if (!apiKey || !apiKey.match(API_KEY_REGEX)) {
306
728
  log("Invalid API key '" + apiKey + "'");
307
729
  return false;
308
730
  }
@@ -310,6 +732,14 @@
310
732
  return true;
311
733
  }
312
734
 
735
+ // get breadcrumb specific setting. When autoBreadcrumbs is true, all individual events are defaulted
736
+ // to true. Otherwise they will all defaul to false. You can set any event specicically and it will override
737
+ // the default.
738
+ function getBreadcrumbSetting(name) {
739
+ var fallback = getSetting("autoBreadcrumbs", true);
740
+ return getSetting(name, fallback);
741
+ }
742
+
313
743
  // Send an error to Bugsnag.
314
744
  function sendToBugsnag(details, metaData) {
315
745
  // Validate the configured API key.
@@ -317,10 +747,11 @@
317
747
  if (!validateApiKey(apiKey) || !eventsRemaining) {
318
748
  return;
319
749
  }
750
+
320
751
  eventsRemaining -= 1;
321
752
 
322
753
  // Check if we should notify for this release stage.
323
- var releaseStage = getSetting("releaseStage");
754
+ var releaseStage = getSetting("releaseStage", "production");
324
755
  var notifyReleaseStages = getSetting("notifyReleaseStages");
325
756
  if (notifyReleaseStages) {
326
757
  var shouldNotify = false;
@@ -346,40 +777,17 @@
346
777
  previousNotification = deduplicate;
347
778
  }
348
779
 
349
- if (lastEvent) {
350
- metaData = metaData || {};
351
- metaData["Last Event"] = eventToMetaData(lastEvent);
352
- }
353
-
354
- // Merge the local and global `metaData`.
355
- var mergedMetaData = merge(merge({}, getSetting("metaData")), metaData);
356
-
357
- // Run any `beforeNotify` function
358
- var beforeNotify = self.beforeNotify;
359
- if (typeof(beforeNotify) === "function") {
360
- var retVal = beforeNotify(details, mergedMetaData);
361
- if (retVal === false) {
362
- return;
363
- }
364
- }
365
-
366
- // Make the request:
367
- //
368
- // - Work out which endpoint to send to.
369
- // - Combine error information with other data such as
370
- // user-agent and locale, `metaData` and settings.
371
- // - Make the HTTP request.
372
- var location = window.location;
373
- console.log(getSetting("endpoint"));
374
- request(getSetting("endpoint") || DEFAULT_NOTIFIER_ENDPOINT, {
780
+ // Build the request payload by combining error information with other data
781
+ // such as user-agent and locale, `metaData` and settings.
782
+ var payload = {
375
783
  notifierVersion: NOTIFIER_VERSION,
376
784
 
377
785
  apiKey: apiKey,
378
- projectRoot: getSetting("projectRoot") || location.protocol + "//" + location.host,
379
- context: getSetting("context") || location.pathname,
786
+ projectRoot: getSetting("projectRoot") || window.location.protocol + "//" + window.location.host,
787
+ context: getSetting("context") || window.location.pathname,
380
788
  userId: getSetting("userId"), // Deprecated, remove in v3
381
789
  user: getSetting("user"),
382
- metaData: mergedMetaData,
790
+ metaData: merge(merge({}, getSetting("metaData")), metaData),
383
791
  releaseStage: releaseStage,
384
792
  appVersion: getSetting("appVersion"),
385
793
 
@@ -394,15 +802,33 @@
394
802
  stacktrace: details.stacktrace,
395
803
  file: details.file,
396
804
  lineNumber: details.lineNumber,
397
- columnNumber: details.columnNumber
398
- });
805
+ columnNumber: details.columnNumber,
806
+ breadcrumbs: breadcrumbs,
807
+ payloadVersion: "3"
808
+ };
809
+
810
+ // Run any `beforeNotify` function
811
+ var beforeNotify = self.beforeNotify;
812
+ if (typeof(beforeNotify) === "function") {
813
+ var retVal = beforeNotify(payload, payload.metaData);
814
+ if (retVal === false) {
815
+ return;
816
+ }
817
+ }
818
+
819
+ if (payload.lineNumber === 0 && (/Script error\.?/).test(payload.message)) {
820
+ return log("Ignoring cross-domain script error. See https://bugsnag.com/docs/notifiers/js/cors");
821
+ }
822
+
823
+ // Make the HTTP request
824
+ request(getSetting("endpoint") || DEFAULT_NOTIFIER_ENDPOINT, payload);
399
825
  }
400
826
 
401
827
  // Generate a browser stacktrace (or approximation) from the current stack.
402
828
  // This is used to add a stacktrace to `Bugsnag.notify` calls, and to add a
403
829
  // stacktrace approximation where we can't get one from an exception.
404
830
  function generateStacktrace() {
405
- var stacktrace;
831
+ var generated, stacktrace;
406
832
  var MAX_FAKE_STACK_SIZE = 10;
407
833
  var ANONYMOUS_FUNCTION_PLACEHOLDER = "[anonymous]";
408
834
 
@@ -410,6 +836,7 @@
410
836
  try {
411
837
  throw new Error("");
412
838
  } catch (exception) {
839
+ generated = "<generated>\n";
413
840
  stacktrace = stacktraceFromException(exception);
414
841
  }
415
842
 
@@ -417,6 +844,7 @@
417
844
  // looping through the list of functions that called this one (and skip
418
845
  // whoever called us).
419
846
  if (!stacktrace) {
847
+ generated = "<generated-ie>\n";
420
848
  var functionStack = [];
421
849
  try {
422
850
  var curr = arguments.callee.caller.caller;
@@ -427,9 +855,7 @@
427
855
  }
428
856
  } catch (e) {
429
857
  log(e);
430
-
431
858
  }
432
-
433
859
  stacktrace = functionStack.join("\n");
434
860
  }
435
861
 
@@ -437,7 +863,7 @@
437
863
  // generateStacktrace() + window.onerror,
438
864
  // generateStacktrace() + notify,
439
865
  // generateStacktrace() + notifyException
440
- return "<generated>\n" + stacktrace;
866
+ return generated + stacktrace;
441
867
  }
442
868
 
443
869
  // Get the stacktrace string from an exception
@@ -445,18 +871,6 @@
445
871
  return exception.stack || exception.backtrace || exception.stacktrace;
446
872
  }
447
873
 
448
- // Populate the event tab of meta-data.
449
- function eventToMetaData(event) {
450
- var tab = {
451
- millisecondsAgo: new Date() - event.timeStamp,
452
- type: event.type,
453
- which: event.which,
454
- target: targetToString(event.target)
455
- };
456
-
457
- return tab;
458
- }
459
-
460
874
  // Convert a DOM element into a string suitable for passing to Bugsnag.
461
875
  function targetToString(target) {
462
876
  if (target) {
@@ -465,13 +879,13 @@
465
879
  if (attrs) {
466
880
  var ret = "<" + target.nodeName.toLowerCase();
467
881
  for (var i = 0; i < attrs.length; i++) {
468
- if (attrs[i].value && attrs[i].value.toString() != "null") {
882
+ if (attrs[i].value && attrs[i].value.toString() !== "null") {
469
883
  ret += " " + attrs[i].name + "=\"" + attrs[i].value + "\"";
470
884
  }
471
885
  }
472
886
  return ret + ">";
473
887
  } else {
474
- // e.g. #document
888
+ // e.g. #document
475
889
  return target.nodeName;
476
890
  }
477
891
  }
@@ -490,14 +904,14 @@
490
904
  if (!window.atob) {
491
905
  shouldCatch = false;
492
906
 
493
- // Disable catching on browsers that support HTML5 ErrorEvents properly.
494
- // This lets debug on unhandled exceptions work.
907
+ // Disable catching on browsers that support HTML5 ErrorEvents properly.
908
+ // This lets debug on unhandled exceptions work.
495
909
  } else if (window.ErrorEvent) {
496
910
  try {
497
911
  if (new window.ErrorEvent("test").colno === 0) {
498
912
  shouldCatch = false;
499
913
  }
500
- } catch(e){ }
914
+ } catch(e){ /* No action needed */ }
501
915
  }
502
916
 
503
917
 
@@ -545,16 +959,30 @@
545
959
  lastScript = null;
546
960
 
547
961
  if (shouldNotify && !ignoreOnError) {
548
-
962
+ var name = exception && exception.name || "window.onerror";
549
963
  sendToBugsnag({
550
- name: exception && exception.name || "window.onerror",
964
+ name: name,
551
965
  message: message,
552
966
  file: url,
553
967
  lineNumber: lineNo,
554
968
  columnNumber: charNo,
555
969
  stacktrace: (exception && stacktraceFromException(exception)) || generateStacktrace(),
556
- severity: "fatal"
970
+ severity: "error"
557
971
  }, metaData);
972
+
973
+ // add the error to the breadcrumbs
974
+ if (getBreadcrumbSetting("autoBreadcrumbsErrors")) {
975
+ self.leaveBreadcrumb({
976
+ type: "error",
977
+ name: name,
978
+ metaData: {
979
+ severity: "error",
980
+ file: url,
981
+ message: message,
982
+ line: lineNo
983
+ }
984
+ });
985
+ }
558
986
  }
559
987
 
560
988
  if (typeof BUGSNAG_TESTING !== "undefined") {
@@ -590,20 +1018,24 @@
590
1018
 
591
1019
  polyFill(window, "setTimeout", hijackTimeFunc);
592
1020
  polyFill(window, "setInterval", hijackTimeFunc);
1021
+
593
1022
  if (window.requestAnimationFrame) {
594
- polyFill(window, "requestAnimationFrame", hijackTimeFunc);
1023
+ polyFill(window, "requestAnimationFrame", function (_super) {
1024
+ return function (callback) {
1025
+ return _super(wrap(callback));
1026
+ };
1027
+ });
595
1028
  }
596
1029
 
597
- var hijackEventFunc = function (_super) {
598
- return function (e, f, capture, secure) {
599
- // HTML lets event-handlers be objects with a handlEvent function,
600
- // we need to change f.handleEvent here, as self.wrap will ignore f.
601
- if (f && f.handleEvent) {
602
- f.handleEvent = wrap(f.handleEvent, {eventHandler: true});
603
- }
604
- return _super.call(this, e, wrap(f, {eventHandler: true}), capture, secure);
605
- };
606
- };
1030
+ if (window.setImmediate) {
1031
+ polyFill(window, "setImmediate", function (_super) {
1032
+ return function () {
1033
+ var args = Array.prototype.slice.call(arguments);
1034
+ args[0] = wrap(args[0]);
1035
+ return _super.apply(this, args);
1036
+ };
1037
+ });
1038
+ }
607
1039
 
608
1040
  // EventTarget is all that's required in modern chrome/opera
609
1041
  // EventTarget + Window + ModalWindow is all that's required in modern FF (there are a few Moz prefixed ones that we're ignoring)
@@ -611,13 +1043,56 @@
611
1043
  "EventTarget Window Node ApplicationCache AudioTrackList ChannelMergerNode CryptoOperation EventSource FileReader HTMLUnknownElement IDBDatabase IDBRequest IDBTransaction KeyOperation MediaController MessagePort ModalWindow Notification SVGElementInstance Screen TextTrack TextTrackCue TextTrackList WebSocket WebSocketWorker Worker XMLHttpRequest XMLHttpRequestEventTarget XMLHttpRequestUpload".replace(/\w+/g, function (global) {
612
1044
  var prototype = window[global] && window[global].prototype;
613
1045
  if (prototype && prototype.hasOwnProperty && prototype.hasOwnProperty("addEventListener")) {
614
- polyFill(prototype, "addEventListener", hijackEventFunc);
1046
+ polyFill(prototype, "addEventListener", function (_super) {
1047
+ return function (e, f, capture, secure) {
1048
+ // HTML lets event-handlers be objects with a handlEvent function,
1049
+ // we need to change f.handleEvent here, as self.wrap will ignore f.
1050
+ try {
1051
+ if (f && f.handleEvent) {
1052
+ f.handleEvent = wrap(f.handleEvent, {eventHandler: true});
1053
+ }
1054
+ } catch (err) {
1055
+ // When selenium is installed, we sometimes get 'Permission denied to access property "handleEvent"'
1056
+ // Because this catch is around Bugsnag library code, it won't catch any user errors
1057
+ log(err);
1058
+ }
1059
+ return _super.call(this, e, wrap(f, {eventHandler: true}), capture, secure);
1060
+ };
1061
+ });
1062
+
615
1063
  // We also need to hack removeEventListener so that you can remove any
616
1064
  // event listeners.
617
- polyFill(prototype, "removeEventListener", hijackEventFunc);
1065
+ polyFill(prototype, "removeEventListener", function (_super) {
1066
+ return function (e, f, capture, secure) {
1067
+ _super.call(this, e, f, capture, secure);
1068
+ return _super.call(this, e, wrap(f), capture, secure);
1069
+ };
1070
+ });
618
1071
  }
619
1072
  });
620
1073
  }
621
1074
 
622
- return self;
623
- });
1075
+ // setup auto breadcrumb tracking
1076
+ trackClicks();
1077
+ trackConsoleLog();
1078
+ trackNavigation();
1079
+
1080
+ window.Bugsnag = self;
1081
+ // If people are using a javascript loader, we should integrate with it.
1082
+ // We don't want to defer instrumenting their code with callbacks however,
1083
+ // so we slightly abuse the intent and continue with our plan of polyfilling
1084
+ // the browser whether or not they ever actually require the module.
1085
+ // This has the nice side-effect of continuing to work when people are using
1086
+ // AMD but loading Bugsnag via a CDN.
1087
+ // It has the not-so-nice side-effect of polluting the global namespace, but
1088
+ // you can easily call Bugsnag.noConflict() to fix that.
1089
+ if (typeof define === "function" && define.amd) {
1090
+ // AMD
1091
+ define([], function () {
1092
+ return self;
1093
+ });
1094
+ } else if (typeof module === "object" && typeof module.exports === "object") {
1095
+ // CommonJS/Browserify
1096
+ module.exports = self;
1097
+ }
1098
+ })(window, window.Bugsnag);
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wat_catcher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.4
4
+ version: 0.10.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Constantine
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-29 00:00:00.000000000 Z
11
+ date: 2016-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler