wat_catcher 0.10.4 → 0.10.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/app/controllers/wat_catcher/catcher_of_wats.rb +4 -4
- data/lib/wat_catcher/version.rb +1 -1
- data/vendor/assets/javascripts/bugsnag.js +601 -126
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dcc9644bbfc8ea819edbca86fb781148929bf4f2
|
4
|
+
data.tar.gz: 9c472e6b2d257386ddd08d5a5f33935b80a9dfe3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b4e6f441e561eecd007a38e0ac1f4e3f1beb6ff35b73688d92c7a1e7b1e66417700c39088f4a526753cbbab28620baeb546ff2a0ca5122ec96a36e18d7e0a979
|
7
|
+
data.tar.gz: 55a12a13df94364a4edb916aa4579bf0909210b4716c8a91d4571922fad85da0e0675480d63327efe733e10d5a91e5977662c825486d798af68d516e2b9ed515
|
data/Gemfile.lock
CHANGED
@@ -5,7 +5,7 @@ module WatCatcher
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
-
|
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
|
}
|
data/lib/wat_catcher/version.rb
CHANGED
@@ -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(
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
//
|
52
|
-
//
|
53
|
-
//
|
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 || "
|
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
|
-
|
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
|
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 (
|
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, "
|
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
|
-
|
139
|
-
|
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 = "
|
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
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
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
|
268
|
-
|
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
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
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
|
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
|
-
|
350
|
-
|
351
|
-
|
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:
|
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
|
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()
|
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
|
-
|
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
|
-
|
494
|
-
|
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:
|
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: "
|
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",
|
1023
|
+
polyFill(window, "requestAnimationFrame", function (_super) {
|
1024
|
+
return function (callback) {
|
1025
|
+
return _super(wrap(callback));
|
1026
|
+
};
|
1027
|
+
});
|
595
1028
|
}
|
596
1029
|
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
}
|
604
|
-
|
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",
|
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",
|
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
|
-
|
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
|
+
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-
|
11
|
+
date: 2016-10-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|