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 +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
|