megatron 0.2.36 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +21 -0
- data/{README.rdoc → README.md} +3 -3
- data/app/assets/images/{400.svg → megatron/400.svg} +0 -0
- data/app/assets/images/{403.svg → megatron/403.svg} +0 -0
- data/app/assets/images/{404.svg → megatron/404.svg} +0 -0
- data/app/assets/images/{408.svg → megatron/408.svg} +0 -0
- data/app/assets/images/{422.svg → megatron/422.svg} +0 -0
- data/app/assets/images/{500.svg → megatron/500.svg} +0 -0
- data/app/assets/images/{502.svg → megatron/502.svg} +0 -0
- data/app/assets/images/{503.svg → megatron/503.svg} +0 -0
- data/app/assets/images/{504.svg → megatron/504.svg} +0 -0
- data/app/assets/images/{888.svg → megatron/888.svg} +0 -0
- data/{public/assets → app/assets/images}/megatron/favicon.ico +0 -0
- data/{public/assets → app/assets/images}/megatron/logo.svg +0 -0
- data/app/assets/javascripts/megatron/_svg.js +55 -0
- data/app/assets/javascripts/megatron/{index.js → megatron.js} +4 -2
- data/app/assets/javascripts/megatron/vendor/bugsnag.js +1256 -0
- data/app/assets/{esvg → svgs}/megatron/account-circle-fill.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/account-circle.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/alerts.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/api.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/arrow_left_circle.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/arrow_left_circle_fill.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/arrow_right_circle.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/arrow_right_circle_fill.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/backups.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/bell.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/bubble.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/bubble_fill.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/check.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/check_circle.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/check_circle_fill.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/check_thin.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/chevron.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/chevron_thin.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/circle-fill.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/circle.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/cli.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/clock_fill.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/clock_line.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/cluster-hosts.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/cluster.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/collections.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/copy.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/dash_circle.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/dash_circle_fill.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/dash_circle_fill_thin.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/dash_circle_thin.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/database_small.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/databases.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/databases_line.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/deployment-beta-line.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/deployment-cassandra.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/deployment-disque.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/deployment-elasticsearch.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/deployment-etcd.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/deployment-influxdb.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/deployment-mongodb-classic.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/deployment-mongodb.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/deployment-mysql.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/deployment-postgresql.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/deployment-rabbitmq.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/deployment-redis.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/deployment-rethinkdb.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/deployment-scylladb.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/deployment.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/deployments.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/download.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/eye.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/eye_closed.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/face-disappointed.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/face-happy.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/gear.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/gear_line.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/gear_round.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/globe.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/help.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/home.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/hourglass.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/info.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/jobs.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/keys.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/lock.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/locked.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/logs.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/minus.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/monitoring.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/payment.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/plus.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/plus_circle.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/plus_circle_fill.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/power.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/processing.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/puzzle.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/question-line.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/question_full.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/restore.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/rocket.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/sandbox.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/scale.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/star.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/tools.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/transporter.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/unlocked.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/users-circle-fill.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/users-circle.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/users.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/warning-circle-fill-thin.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/warning.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/warning_circle.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/warning_circle_fill.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/warning_circle_thin.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/watch.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/x.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/x_circle.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/x_circle_fill.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/x_circle_fill_thin.svg +0 -0
- data/app/assets/{esvg → svgs}/megatron/x_circle_thin.svg +0 -0
- data/app/controllers/megatron/errors_controller.rb +3 -0
- data/app/helpers/megatron/application_helper.rb +0 -95
- data/app/helpers/megatron/icon_helper.rb +11 -7
- data/app/helpers/megatron/layout_helper.rb +2 -10
- data/app/views/layouts/megatron/default.html.slim +76 -0
- data/app/views/layouts/megatron/errors.html.slim +8 -8
- data/app/views/megatron/shared/_flash_messages.html.slim +6 -0
- data/app/views/megatron/shared/_head.html.slim +23 -0
- data/app/views/megatron/shared/_main_content.html.slim +6 -3
- data/lib/megatron/engine.rb +0 -7
- data/lib/megatron/slim_code_filter.rb +21 -0
- data/lib/megatron/version.rb +1 -1
- data/lib/megatron.rb +10 -6
- data/public/{assets/megatron/megatron-0.2.36.css → megatron-0.3.0.css} +1825 -1879
- data/public/megatron-0.3.0.css.gz +0 -0
- data/public/{assets/megatron/megatron-0.2.36.js → megatron-0.3.0.js} +7 -7
- data/public/megatron-0.3.0.js.gz +0 -0
- data/public/megatron-0.3.0.map.json +1 -0
- data/public/megatron-error-pages-0.3.0.css +901 -0
- data/public/megatron-error-pages-0.3.0.css.gz +0 -0
- metadata +149 -135
- data/MIT-LICENSE +0 -20
- data/Rakefile +0 -209
- data/app/assets/javascripts/megatron/esvg.js +0 -55
- data/app/views/layouts/megatron/application.html.slim +0 -67
- data/config/esvg.yml +0 -8
- data/config/routes.rb +0 -2
- data/public/assets/megatron/megatron-0.2.36.css.gz +0 -0
- data/public/assets/megatron/megatron-0.2.36.css.map +0 -7
- data/public/assets/megatron/megatron-0.2.36.js.gz +0 -0
- data/public/assets/megatron/megatron-0.2.36.map.json +0 -1
- data/public/assets/megatron/megatron-error-pages-0.2.36.css +0 -1
- data/public/assets/megatron/megatron-error-pages-0.2.36.css.gz +0 -0
@@ -0,0 +1,1256 @@
|
|
1
|
+
//
|
2
|
+
// Version 3.0.7
|
3
|
+
//
|
4
|
+
// https://github.com/bugsnag/bugsnag-js/releases/tag/v3.0.7
|
5
|
+
//
|
6
|
+
// **Bugsnag.js** is the official JavaScript notifier for
|
7
|
+
// [Bugsnag](https://bugsnag.com).
|
8
|
+
//
|
9
|
+
// Bugsnag gives you instant notification of errors and
|
10
|
+
// exceptions in your website's JavaScript code.
|
11
|
+
//
|
12
|
+
// Bugsnag.js is incredibly small, and has no external dependencies (not even
|
13
|
+
// jQuery!) so you can safely use it on any website.
|
14
|
+
//
|
15
|
+
|
16
|
+
// The `Bugsnag` object is the only globally exported variable
|
17
|
+
(function (window, old) {
|
18
|
+
var self = {},
|
19
|
+
lastScript,
|
20
|
+
previousNotification,
|
21
|
+
shouldCatch = true,
|
22
|
+
ignoreOnError = 0,
|
23
|
+
breadcrumbs = [],
|
24
|
+
breadcrumbHardLimit = 40,
|
25
|
+
placeholderErrorName = "BugsnagNotify",
|
26
|
+
|
27
|
+
// We've seen cases where individual clients can infinite loop sending us errors
|
28
|
+
// (in some cases 10,000+ errors per page). This limit is at the point where
|
29
|
+
// you've probably learned everything useful there is to debug the problem,
|
30
|
+
// and we're happy to under-estimate the count to save the client (and Bugsnag's) resources.
|
31
|
+
eventsRemaining = 10,
|
32
|
+
// The default depth of attached metadata which is parsed before truncation. It
|
33
|
+
// is configurable via the `maxDepth` setting.
|
34
|
+
maxPayloadDepth = 5;
|
35
|
+
|
36
|
+
|
37
|
+
// Set default breadcrumbLimit to 20, so we don't send a giant payload.
|
38
|
+
// This can be overridden up to the breadcrumbHardLimit
|
39
|
+
self.breadcrumbLimit = 20;
|
40
|
+
|
41
|
+
// #### Bugsnag.noConflict
|
42
|
+
//
|
43
|
+
// This is obsolete with UMD, as we cannot assume the global scope is polluted with
|
44
|
+
// the Bugsnag object anyway. In this case, it's up to the host Javascript file to
|
45
|
+
// correctly utilise this functionality.
|
46
|
+
//
|
47
|
+
// Maybe it's worth removing all together, if we're loading via any UMD method.
|
48
|
+
self.noConflict = function() {
|
49
|
+
window.Bugsnag = old;
|
50
|
+
if (typeof old === "undefined") {
|
51
|
+
delete window.Bugsnag;
|
52
|
+
}
|
53
|
+
return self;
|
54
|
+
};
|
55
|
+
|
56
|
+
// ### Bugsnag.refresh
|
57
|
+
//
|
58
|
+
// Resets the Bugsnag rate limit. If you have a large single-page app, you may
|
59
|
+
// wish to call this in your router to avoid exception reports being thrown
|
60
|
+
// away.
|
61
|
+
//
|
62
|
+
// By default Bugsnag aggressively limits the number of exception reports from
|
63
|
+
// one page load. This protects both the client's browser and our servers in
|
64
|
+
// cases where exceptions are thrown in tight loops or scroll handlers.
|
65
|
+
self.refresh = function() {
|
66
|
+
eventsRemaining = 10;
|
67
|
+
};
|
68
|
+
|
69
|
+
//
|
70
|
+
// ### Manual error notification (public methods)
|
71
|
+
//
|
72
|
+
|
73
|
+
// #### Bugsnag.notifyException
|
74
|
+
//
|
75
|
+
// Notify Bugsnag about a given `exception`, typically that you've caught
|
76
|
+
// with a `try/catch` statement or that you've generated yourself.
|
77
|
+
//
|
78
|
+
// It's almost always better to let an exception bubble rather than catching
|
79
|
+
// it, as that gives more consistent behaviour across browsers. Consider
|
80
|
+
// re-throwing instead of calling .notifyException.
|
81
|
+
//
|
82
|
+
// Since most JavaScript exceptions use the `Error` class, we also allow
|
83
|
+
// you to provide a custom error name when calling `notifyException`.
|
84
|
+
//
|
85
|
+
// The default value is "warning" and "error" and "info" are also supported by the
|
86
|
+
// backend, all other values cause the notification to be dropped; and you
|
87
|
+
// will not see it in your dashboard.
|
88
|
+
self.notifyException = function (exception, name, metaData, severity) {
|
89
|
+
if (!exception) {
|
90
|
+
var message = "Bugsnag.notifyException() was called with no arguments";
|
91
|
+
log(message);
|
92
|
+
self.notify(placeholderErrorName, message);
|
93
|
+
return;
|
94
|
+
}
|
95
|
+
|
96
|
+
if (typeof exception === "string") {
|
97
|
+
log(
|
98
|
+
"Bugsnag.notifyException() was called with a string. Expected instance of Error. " +
|
99
|
+
"To send a custom message instantiate a new Error or use Bugsnag.notify('<string>')." +
|
100
|
+
" see https://docs.bugsnag.com/platforms/browsers/#reporting-handled-exceptions"
|
101
|
+
);
|
102
|
+
// pass through to notify()
|
103
|
+
self.notify.apply(null, arguments);
|
104
|
+
return;
|
105
|
+
}
|
106
|
+
|
107
|
+
if (name && typeof name !== "string") {
|
108
|
+
metaData = name;
|
109
|
+
name = undefined;
|
110
|
+
}
|
111
|
+
if (!metaData) {
|
112
|
+
metaData = {};
|
113
|
+
}
|
114
|
+
addScriptToMetaData(metaData);
|
115
|
+
|
116
|
+
sendToBugsnag({
|
117
|
+
name: name || exception.name,
|
118
|
+
message: exception.message || exception.description,
|
119
|
+
stacktrace: stacktraceFromException(exception) || generateStacktrace(),
|
120
|
+
file: exception.fileName || exception.sourceURL,
|
121
|
+
lineNumber: exception.lineNumber || exception.line,
|
122
|
+
columnNumber: exception.columnNumber ? exception.columnNumber + 1 : undefined,
|
123
|
+
severity: severity || "warning"
|
124
|
+
}, metaData);
|
125
|
+
};
|
126
|
+
|
127
|
+
// #### Bugsnag.notify
|
128
|
+
//
|
129
|
+
// Notify Bugsnag about an error by passing in a `name` and `message`,
|
130
|
+
// without requiring an exception.
|
131
|
+
self.notify = function (name, message, metaData, severity) {
|
132
|
+
if (!name) {
|
133
|
+
name = placeholderErrorName;
|
134
|
+
message = "Bugsnag.notify() was called with no arguments";
|
135
|
+
log(message);
|
136
|
+
}
|
137
|
+
|
138
|
+
sendToBugsnag({
|
139
|
+
name: name,
|
140
|
+
message: message,
|
141
|
+
stacktrace: generateStacktrace(),
|
142
|
+
// These are defaults so that 'bugsnag.notify()' calls show up in old IE,
|
143
|
+
// newer browsers get a legit stacktrace from generateStacktrace().
|
144
|
+
file: window.location.toString(),
|
145
|
+
lineNumber: 1,
|
146
|
+
severity: severity || "warning"
|
147
|
+
}, metaData);
|
148
|
+
};
|
149
|
+
|
150
|
+
// #### Bugsnag.leaveBreadcrumb(value, [metaData])
|
151
|
+
//
|
152
|
+
// Add a breadcrumb to the array of breadcrumbs to be sent to Bugsnag when the next exception occurs
|
153
|
+
// - `value` (string|object) If this is an object, it will be used as the entire breadcrumb object
|
154
|
+
// and any missing `type`, `name` or `timestamp` fields will get default values.
|
155
|
+
// if this is a string and metaData is provided, then `value` will be used as the `name` of the
|
156
|
+
// breadcrumb.
|
157
|
+
// if `value` is a string and is the only argument the breadcrumb will have `manual` type and
|
158
|
+
// the value will be used as the `message` field of `metaData`.
|
159
|
+
//
|
160
|
+
// - `metadata` (optional, object) - Additional information about the breadcrumb. Values limited to 140 characters.
|
161
|
+
self.leaveBreadcrumb = function(value, metaData) {
|
162
|
+
var DEFAULT_TYPE = "manual";
|
163
|
+
|
164
|
+
// default crumb
|
165
|
+
var crumb = {
|
166
|
+
type: DEFAULT_TYPE,
|
167
|
+
name: "Manual",
|
168
|
+
timestamp: new Date().getTime()
|
169
|
+
};
|
170
|
+
|
171
|
+
switch (typeof value) {
|
172
|
+
case "object":
|
173
|
+
crumb = merge(crumb, value);
|
174
|
+
break;
|
175
|
+
case "string":
|
176
|
+
if (metaData && typeof metaData === "object") {
|
177
|
+
crumb = merge(crumb, {
|
178
|
+
name: value,
|
179
|
+
metaData: metaData
|
180
|
+
});
|
181
|
+
} else {
|
182
|
+
crumb.metaData = { message: value };
|
183
|
+
}
|
184
|
+
break;
|
185
|
+
default:
|
186
|
+
log("expecting 1st argument to leaveBreadcrumb to be a 'string' or 'object', got " + typeof value);
|
187
|
+
return;
|
188
|
+
}
|
189
|
+
|
190
|
+
// Validate breadcrumb type and replace invalid type with default.
|
191
|
+
var VALID_TYPES = [DEFAULT_TYPE, "error", "log", "navigation", "process", "request", "state", "user"];
|
192
|
+
var validType = false;
|
193
|
+
for (var i = 0; i < VALID_TYPES.length; i++) {
|
194
|
+
if (VALID_TYPES[i] === crumb.type) {
|
195
|
+
validType = true;
|
196
|
+
break;
|
197
|
+
}
|
198
|
+
}
|
199
|
+
if (!validType) {
|
200
|
+
log("Converted invalid breadcrumb type '" + crumb.type + "' to '" + DEFAULT_TYPE + "'");
|
201
|
+
crumb.type = DEFAULT_TYPE;
|
202
|
+
}
|
203
|
+
|
204
|
+
var lastCrumb = breadcrumbs.slice(-1)[0];
|
205
|
+
if (breadcrumbsAreEqual(crumb, lastCrumb)) {
|
206
|
+
lastCrumb.count = lastCrumb.count || 1;
|
207
|
+
lastCrumb.count++;
|
208
|
+
} else {
|
209
|
+
var breadcrumbLimit = Math.min(self.breadcrumbLimit, breadcrumbHardLimit);
|
210
|
+
crumb.name = truncate(crumb.name, 32);
|
211
|
+
breadcrumbs.push(truncateDeep(crumb, 140));
|
212
|
+
|
213
|
+
// limit breadcrumb trail length, so the payload doesn't get too large
|
214
|
+
if (breadcrumbs.length > breadcrumbLimit) {
|
215
|
+
breadcrumbs = breadcrumbs.slice(-breadcrumbLimit);
|
216
|
+
}
|
217
|
+
}
|
218
|
+
};
|
219
|
+
|
220
|
+
function breadcrumbsAreEqual(crumb1, crumb2) {
|
221
|
+
return crumb1 && crumb2 &&
|
222
|
+
crumb1.type === crumb2.type &&
|
223
|
+
crumb1.name === crumb2.name &&
|
224
|
+
isEqual(crumb1.metaData, crumb2.metaData);
|
225
|
+
}
|
226
|
+
|
227
|
+
// Return a function acts like the given function, but reports
|
228
|
+
// any exceptions to Bugsnag before re-throwing them.
|
229
|
+
//
|
230
|
+
// This is not a public function because it can only be used if
|
231
|
+
// the exception is not caught after being thrown out of this function.
|
232
|
+
//
|
233
|
+
// If you call wrap twice on the same function, it'll give you back the
|
234
|
+
// same wrapped function. This lets removeEventListener to continue to
|
235
|
+
// work.
|
236
|
+
function wrap(_super) {
|
237
|
+
try {
|
238
|
+
if (typeof _super !== "function") {
|
239
|
+
return _super;
|
240
|
+
}
|
241
|
+
if (!_super.bugsnag) {
|
242
|
+
var currentScript = getCurrentScript();
|
243
|
+
_super.bugsnag = function () {
|
244
|
+
lastScript = currentScript;
|
245
|
+
|
246
|
+
// We set shouldCatch to false on IE < 10 because catching the error ruins the file/line as reported in window.onerror,
|
247
|
+
// We set shouldCatch to false on Chrome/Safari because it interferes with "break on unhandled exception"
|
248
|
+
// All other browsers need shouldCatch to be true, as they don't pass the exception object to window.onerror
|
249
|
+
if (shouldCatch) {
|
250
|
+
try {
|
251
|
+
return _super.apply(this, arguments);
|
252
|
+
} catch (e) {
|
253
|
+
if (getSetting("autoNotify", true)) {
|
254
|
+
self.notifyException(e, null, null, "error");
|
255
|
+
ignoreNextOnError();
|
256
|
+
}
|
257
|
+
throw e;
|
258
|
+
} finally {
|
259
|
+
lastScript = null;
|
260
|
+
}
|
261
|
+
} else {
|
262
|
+
var ret = _super.apply(this, arguments);
|
263
|
+
// in case of error, this is set to null in window.onerror
|
264
|
+
lastScript = null;
|
265
|
+
return ret;
|
266
|
+
}
|
267
|
+
};
|
268
|
+
_super.bugsnag.bugsnag = _super.bugsnag;
|
269
|
+
}
|
270
|
+
return _super.bugsnag;
|
271
|
+
|
272
|
+
// This can happen if _super is not a normal javascript function.
|
273
|
+
// For example, see https://github.com/bugsnag/bugsnag-js/issues/28
|
274
|
+
} catch (e) {
|
275
|
+
return _super;
|
276
|
+
}
|
277
|
+
}
|
278
|
+
|
279
|
+
var _hasAddEventListener = (typeof window.addEventListener !== "undefined");
|
280
|
+
|
281
|
+
// Setup breadcrumbs for click events
|
282
|
+
function setupClickBreadcrumbs() {
|
283
|
+
if (!_hasAddEventListener) {
|
284
|
+
return;
|
285
|
+
}
|
286
|
+
|
287
|
+
var callback = function(event) {
|
288
|
+
if(!getBreadcrumbSetting("autoBreadcrumbsClicks")) {
|
289
|
+
return;
|
290
|
+
}
|
291
|
+
|
292
|
+
var targetText, targetSelector;
|
293
|
+
// Cross origin security might prevent us from accessing the event target
|
294
|
+
|
295
|
+
try {
|
296
|
+
targetText = nodeText(event.target);
|
297
|
+
targetSelector = nodeLabel(event.target);
|
298
|
+
} catch (e) {
|
299
|
+
targetText = "[hidden]";
|
300
|
+
targetSelector = "[hidden]";
|
301
|
+
log("Cross domain error when tracking click event. See https://docs.bugsnag.com/platforms/browsers/faq/#3-cross-origin-script-errors");
|
302
|
+
}
|
303
|
+
|
304
|
+
self.leaveBreadcrumb({
|
305
|
+
type: "user",
|
306
|
+
name: "UI click",
|
307
|
+
metaData: {
|
308
|
+
targetText: targetText,
|
309
|
+
targetSelector: targetSelector
|
310
|
+
}
|
311
|
+
});
|
312
|
+
};
|
313
|
+
|
314
|
+
window.addEventListener("click", callback, true);
|
315
|
+
}
|
316
|
+
|
317
|
+
// stub functions for old browsers
|
318
|
+
self.enableAutoBreadcrumbsConsole = function() {};
|
319
|
+
self.disableAutoBreadcrumbsConsole = function() {};
|
320
|
+
|
321
|
+
// Setup breadcrumbs for console.log, console.warn, console.error
|
322
|
+
function setupConsoleBreadcrumbs(){
|
323
|
+
|
324
|
+
function trackLog(severity, args) {
|
325
|
+
if(!getBreadcrumbSetting("autoBreadcrumbsConsole")) {
|
326
|
+
return;
|
327
|
+
}
|
328
|
+
|
329
|
+
self.leaveBreadcrumb({
|
330
|
+
type: "log",
|
331
|
+
name: "Console output",
|
332
|
+
metaData: {
|
333
|
+
severity: severity,
|
334
|
+
message: Array.prototype.slice.call(args).join(", ")
|
335
|
+
}
|
336
|
+
});
|
337
|
+
}
|
338
|
+
|
339
|
+
// feature detection for console.log
|
340
|
+
if(typeof window.console === "undefined") {
|
341
|
+
return;
|
342
|
+
}
|
343
|
+
|
344
|
+
// keep track of functions that we will need to hijack
|
345
|
+
var nativeLog = console.log,
|
346
|
+
nativeWarn = console.warn,
|
347
|
+
nativeError = console.error;
|
348
|
+
|
349
|
+
self.enableAutoBreadcrumbsConsole = function() {
|
350
|
+
self.autoBreadcrumbsConsole = true;
|
351
|
+
|
352
|
+
enhance(console, "log", function() {
|
353
|
+
trackLog("log", arguments);
|
354
|
+
});
|
355
|
+
|
356
|
+
enhance(console, "warn", function() {
|
357
|
+
trackLog("warn", arguments);
|
358
|
+
});
|
359
|
+
|
360
|
+
enhance(console, "error", function() {
|
361
|
+
trackLog("error", arguments);
|
362
|
+
});
|
363
|
+
};
|
364
|
+
|
365
|
+
self.disableAutoBreadcrumbsConsole = function() {
|
366
|
+
self.autoBreadcrumbsConsole = false;
|
367
|
+
|
368
|
+
console.log = nativeLog;
|
369
|
+
console.warn = nativeWarn;
|
370
|
+
console.error = nativeError;
|
371
|
+
};
|
372
|
+
|
373
|
+
if(getBreadcrumbSetting("autoBreadcrumbsConsole")) {
|
374
|
+
self.enableAutoBreadcrumbsConsole();
|
375
|
+
}
|
376
|
+
}
|
377
|
+
|
378
|
+
// stub functions for old browsers
|
379
|
+
self.enableAutoBreadcrumbsNavigation = function() {};
|
380
|
+
self.disableAutoBreadcrumbsNavigation = function() {};
|
381
|
+
|
382
|
+
// Setup breadcrumbs for history navigation events
|
383
|
+
function setupNavigationBreadcrumbs() {
|
384
|
+
|
385
|
+
function parseHash(url) {
|
386
|
+
return url.split("#")[1] || "";
|
387
|
+
}
|
388
|
+
|
389
|
+
function buildHashChange(event) {
|
390
|
+
var oldURL = event.oldURL,
|
391
|
+
newURL = event.newURL,
|
392
|
+
metaData = {};
|
393
|
+
|
394
|
+
// not supported in old browsers
|
395
|
+
if (oldURL && newURL) {
|
396
|
+
metaData.from = parseHash(oldURL);
|
397
|
+
metaData.to = parseHash(newURL);
|
398
|
+
} else {
|
399
|
+
metaData.to = location.hash;
|
400
|
+
}
|
401
|
+
|
402
|
+
return {
|
403
|
+
type: "navigation",
|
404
|
+
name: "Hash changed",
|
405
|
+
metaData: metaData
|
406
|
+
};
|
407
|
+
}
|
408
|
+
|
409
|
+
function buildPopState() {
|
410
|
+
return {
|
411
|
+
type: "navigation",
|
412
|
+
name: "Navigated back"
|
413
|
+
};
|
414
|
+
}
|
415
|
+
|
416
|
+
function buildPageHide() {
|
417
|
+
return {
|
418
|
+
type: "navigation",
|
419
|
+
name: "Page hidden"
|
420
|
+
};
|
421
|
+
}
|
422
|
+
|
423
|
+
function buildPageShow() {
|
424
|
+
return {
|
425
|
+
type: "navigation",
|
426
|
+
name: "Page shown"
|
427
|
+
};
|
428
|
+
}
|
429
|
+
|
430
|
+
function buildLoad() {
|
431
|
+
return {
|
432
|
+
type: "navigation",
|
433
|
+
name: "Page loaded"
|
434
|
+
};
|
435
|
+
}
|
436
|
+
|
437
|
+
function buildDOMContentLoaded() {
|
438
|
+
return {
|
439
|
+
type: "navigation",
|
440
|
+
name: "DOMContentLoaded"
|
441
|
+
};
|
442
|
+
}
|
443
|
+
|
444
|
+
function buildStateChange(name, state, title, url) {
|
445
|
+
var currentPath = location.pathname + location.search + location.hash;
|
446
|
+
return {
|
447
|
+
type: "navigation",
|
448
|
+
name: "History " + name,
|
449
|
+
metaData: {
|
450
|
+
from: currentPath,
|
451
|
+
to: url || currentPath,
|
452
|
+
prevState: history.state,
|
453
|
+
nextState: state
|
454
|
+
}
|
455
|
+
};
|
456
|
+
}
|
457
|
+
|
458
|
+
function buildPushState(state, title, url) {
|
459
|
+
return buildStateChange("pushState", state, title, url);
|
460
|
+
}
|
461
|
+
|
462
|
+
function buildReplaceState(state, title, url) {
|
463
|
+
return buildStateChange("replaceState", state, title, url);
|
464
|
+
}
|
465
|
+
|
466
|
+
// functional fu to make it easier to setup event listeners
|
467
|
+
function wrapBuilder(builder) {
|
468
|
+
return function() {
|
469
|
+
if(!getBreadcrumbSetting("autoBreadcrumbsNavigation")) {
|
470
|
+
return;
|
471
|
+
}
|
472
|
+
|
473
|
+
self.leaveBreadcrumb(builder.apply(null, arguments));
|
474
|
+
};
|
475
|
+
}
|
476
|
+
|
477
|
+
// check for browser support
|
478
|
+
if (!_hasAddEventListener ||
|
479
|
+
!window.history ||
|
480
|
+
!window.history.state ||
|
481
|
+
!window.history.pushState ||
|
482
|
+
!window.history.pushState.bind
|
483
|
+
) {
|
484
|
+
return;
|
485
|
+
}
|
486
|
+
|
487
|
+
// keep track of native functions
|
488
|
+
var nativePushState = history.pushState,
|
489
|
+
nativeReplaceState = history.replaceState;
|
490
|
+
|
491
|
+
// create enable function
|
492
|
+
self.enableAutoBreadcrumbsNavigation = function() {
|
493
|
+
self.autoBreadcrumbsNavigation = true;
|
494
|
+
// create hooks for pushstate and replaceState
|
495
|
+
enhance(history, "pushState", wrapBuilder(buildPushState));
|
496
|
+
enhance(history, "replaceState", wrapBuilder(buildReplaceState));
|
497
|
+
};
|
498
|
+
|
499
|
+
// create disable function
|
500
|
+
self.disableAutoBreadcrumbsNavigation = function() {
|
501
|
+
self.autoBreadcrumbsNavigation = false;
|
502
|
+
// restore native functions
|
503
|
+
history.pushState = nativePushState;
|
504
|
+
history.replaceState = nativeReplaceState;
|
505
|
+
};
|
506
|
+
|
507
|
+
window.addEventListener("hashchange", wrapBuilder(buildHashChange), true);
|
508
|
+
window.addEventListener("popstate", wrapBuilder(buildPopState), true);
|
509
|
+
window.addEventListener("pagehide", wrapBuilder(buildPageHide), true);
|
510
|
+
window.addEventListener("pageshow", wrapBuilder(buildPageShow), true);
|
511
|
+
window.addEventListener("load", wrapBuilder(buildLoad), true);
|
512
|
+
window.addEventListener("DOMContentLoaded", wrapBuilder(buildDOMContentLoaded), true);
|
513
|
+
|
514
|
+
if(getBreadcrumbSetting("autoBreadcrumbsNavigation")) {
|
515
|
+
self.enableAutoBreadcrumbsNavigation();
|
516
|
+
}
|
517
|
+
}
|
518
|
+
|
519
|
+
self.enableAutoBreadcrumbsErrors = function() {
|
520
|
+
self.autoBreadcrumbsErrors = true;
|
521
|
+
};
|
522
|
+
|
523
|
+
self.disableAutoBreadcrumbsErrors = function() {
|
524
|
+
self.autoBreadcrumbsErrors = false;
|
525
|
+
};
|
526
|
+
|
527
|
+
self.enableAutoBreadcrumbsClicks = function() {
|
528
|
+
self.autoBreadcrumbsClicks = true;
|
529
|
+
};
|
530
|
+
|
531
|
+
self.disableAutoBreadcrumbsClicks = function() {
|
532
|
+
self.autoBreadcrumbsClicks = false;
|
533
|
+
};
|
534
|
+
|
535
|
+
self.enableAutoBreadcrumbs = function() {
|
536
|
+
self.enableAutoBreadcrumbsClicks();
|
537
|
+
self.enableAutoBreadcrumbsConsole();
|
538
|
+
self.enableAutoBreadcrumbsErrors();
|
539
|
+
self.enableAutoBreadcrumbsNavigation();
|
540
|
+
};
|
541
|
+
|
542
|
+
self.disableAutoBreadcrumbs = function() {
|
543
|
+
self.disableAutoBreadcrumbsClicks();
|
544
|
+
self.disableAutoBreadcrumbsConsole();
|
545
|
+
self.disableAutoBreadcrumbsErrors();
|
546
|
+
self.disableAutoBreadcrumbsNavigation();
|
547
|
+
};
|
548
|
+
|
549
|
+
//
|
550
|
+
// ### Script tag tracking
|
551
|
+
//
|
552
|
+
|
553
|
+
// To emulate document.currentScript we use document.scripts.last.
|
554
|
+
// This only works while synchronous scripts are running, so we track
|
555
|
+
// that here.
|
556
|
+
var synchronousScriptsRunning = document.readyState !== "complete";
|
557
|
+
function loadCompleted() {
|
558
|
+
synchronousScriptsRunning = false;
|
559
|
+
}
|
560
|
+
|
561
|
+
// from jQuery. We don't have quite such tight bounds as they do if
|
562
|
+
// we end up on the window.onload event as we don't try and hack
|
563
|
+
// the .scrollLeft() fix in because it doesn't work in frames so
|
564
|
+
// we'd need these fallbacks anyway.
|
565
|
+
// The worst that can happen is we group an event handler that fires
|
566
|
+
// before us into the last script tag.
|
567
|
+
if (document.addEventListener) {
|
568
|
+
document.addEventListener("DOMContentLoaded", loadCompleted, true);
|
569
|
+
window.addEventListener("load", loadCompleted, true);
|
570
|
+
} else {
|
571
|
+
window.attachEvent("onload", loadCompleted);
|
572
|
+
}
|
573
|
+
|
574
|
+
function getCurrentScript() {
|
575
|
+
var script = document.currentScript || lastScript;
|
576
|
+
|
577
|
+
if (!script && synchronousScriptsRunning) {
|
578
|
+
var scripts = document.scripts || document.getElementsByTagName("script");
|
579
|
+
script = scripts[scripts.length - 1];
|
580
|
+
}
|
581
|
+
|
582
|
+
return script;
|
583
|
+
}
|
584
|
+
|
585
|
+
function addScriptToMetaData(metaData) {
|
586
|
+
var script = getCurrentScript();
|
587
|
+
|
588
|
+
if (script) {
|
589
|
+
metaData.script = {
|
590
|
+
src: script.src,
|
591
|
+
content: getSetting("inlineScript", true) ? script.innerHTML : ""
|
592
|
+
};
|
593
|
+
}
|
594
|
+
}
|
595
|
+
|
596
|
+
//
|
597
|
+
// ### Helpers & Setup
|
598
|
+
//
|
599
|
+
|
600
|
+
// Compile regular expressions upfront.
|
601
|
+
var API_KEY_REGEX = /^[0-9a-f]{32}$/i;
|
602
|
+
var FUNCTION_REGEX = /function\s*([\w\-$]+)?\s*\(/i;
|
603
|
+
|
604
|
+
// Set up default notifier settings.
|
605
|
+
var DEFAULT_BASE_ENDPOINT = "https://notify.bugsnag.com/";
|
606
|
+
var DEFAULT_NOTIFIER_ENDPOINT = DEFAULT_BASE_ENDPOINT + "js";
|
607
|
+
var NOTIFIER_VERSION = "3.0.7";
|
608
|
+
|
609
|
+
// Keep a reference to the currently executing script in the DOM.
|
610
|
+
// We'll use this later to extract settings from attributes.
|
611
|
+
var scripts = document.getElementsByTagName("script");
|
612
|
+
var thisScript = scripts[scripts.length - 1];
|
613
|
+
|
614
|
+
// Replace existing function on object with custom one, but still call the original afterwards
|
615
|
+
// example:
|
616
|
+
// enhance(console, 'log', function() {
|
617
|
+
// /* custom behavior */
|
618
|
+
// })
|
619
|
+
function enhance(object, property, newFunction) {
|
620
|
+
var oldFunction = object[property];
|
621
|
+
object[property] = function() {
|
622
|
+
newFunction.apply(this, arguments);
|
623
|
+
if (typeof oldFunction === "function") {
|
624
|
+
oldFunction.apply(this, arguments);
|
625
|
+
}
|
626
|
+
};
|
627
|
+
}
|
628
|
+
|
629
|
+
// Simple logging function that wraps `console.log` if available.
|
630
|
+
// This is useful for warning about configuration issues
|
631
|
+
// eg. forgetting to set an API key.
|
632
|
+
function log(msg) {
|
633
|
+
var disableLog = getSetting("disableLog");
|
634
|
+
|
635
|
+
var console = window.console;
|
636
|
+
if (console !== undefined && console.log !== undefined && !disableLog) {
|
637
|
+
console.log("[Bugsnag] " + msg);
|
638
|
+
}
|
639
|
+
}
|
640
|
+
|
641
|
+
// Compare if two objects are equal.
|
642
|
+
function isEqual(obj1, obj2) {
|
643
|
+
return serialize(obj1) === serialize(obj2);
|
644
|
+
}
|
645
|
+
|
646
|
+
// extract text content from a element
|
647
|
+
function nodeText(el) {
|
648
|
+
var text = el.textContent || el.innerText || "";
|
649
|
+
|
650
|
+
if (el.type === "submit" || el.type === "button") {
|
651
|
+
text = el.value;
|
652
|
+
}
|
653
|
+
|
654
|
+
text = text.replace(/^\s+|\s+$/g, ""); // trim whitespace
|
655
|
+
return truncate(text, 140);
|
656
|
+
}
|
657
|
+
|
658
|
+
// Create a label from tagname, id and css class of the element
|
659
|
+
function nodeLabel(el) {
|
660
|
+
var parts = [el.tagName];
|
661
|
+
|
662
|
+
if (el.id) {
|
663
|
+
parts.push("#" + el.id);
|
664
|
+
}
|
665
|
+
|
666
|
+
if (el.className && el.className.length) {
|
667
|
+
var classString = "." + el.className.split(" ").join(".");
|
668
|
+
parts.push(classString);
|
669
|
+
}
|
670
|
+
|
671
|
+
var label = parts.join("");
|
672
|
+
|
673
|
+
if (!document.querySelectorAll || !Array.prototype.indexOf) {
|
674
|
+
// can't get much more advanced with the current browser
|
675
|
+
return label;
|
676
|
+
}
|
677
|
+
|
678
|
+
try {
|
679
|
+
if (document.querySelectorAll(label).length === 1) {
|
680
|
+
return label;
|
681
|
+
}
|
682
|
+
} catch (e) {
|
683
|
+
// sometime the query selector can be invalid, for example, if the id attribute is anumber.
|
684
|
+
// in these cases, just return the label as is.
|
685
|
+
return label;
|
686
|
+
}
|
687
|
+
|
688
|
+
// try to get a more specific selector if this one matches more than one element
|
689
|
+
if (el.parentNode.childNodes.length > 1) {
|
690
|
+
var index = Array.prototype.indexOf.call(el.parentNode.childNodes, el) + 1;
|
691
|
+
label = label + ":nth-child(" + index + ")";
|
692
|
+
}
|
693
|
+
|
694
|
+
if (document.querySelectorAll(label).length === 1) {
|
695
|
+
return label;
|
696
|
+
}
|
697
|
+
|
698
|
+
// try prepending the parent node selector
|
699
|
+
if (el.parentNode) {
|
700
|
+
return nodeLabel(el.parentNode) + " > " + label;
|
701
|
+
}
|
702
|
+
|
703
|
+
return label;
|
704
|
+
}
|
705
|
+
|
706
|
+
function truncate(value, length) {
|
707
|
+
var OMISSION = "(...)";
|
708
|
+
|
709
|
+
if (value && value.length > length) {
|
710
|
+
return value.slice(0, length - OMISSION.length) + OMISSION;
|
711
|
+
} else {
|
712
|
+
return value;
|
713
|
+
}
|
714
|
+
}
|
715
|
+
|
716
|
+
function isArray(arg) {
|
717
|
+
return Object.prototype.toString.call(arg) === "[object Array]";
|
718
|
+
}
|
719
|
+
|
720
|
+
// truncate all string values in nested object
|
721
|
+
function truncateDeep(object, length, depth) {
|
722
|
+
var newDepth = (depth || 0) + 1;
|
723
|
+
var setting = getSetting("maxDepth", maxPayloadDepth);
|
724
|
+
|
725
|
+
if (depth > setting) {
|
726
|
+
return "[RECURSIVE]";
|
727
|
+
}
|
728
|
+
|
729
|
+
// Handle truncating strings
|
730
|
+
if (typeof object === "string") {
|
731
|
+
return truncate(object, length);
|
732
|
+
|
733
|
+
// Handle truncating array contents
|
734
|
+
} else if (isArray(object)) {
|
735
|
+
var newArray = [];
|
736
|
+
for (var i = 0; i < object.length; i++) {
|
737
|
+
newArray[i] = truncateDeep(object[i], length, newDepth);
|
738
|
+
}
|
739
|
+
return newArray;
|
740
|
+
|
741
|
+
// Handle truncating object keys
|
742
|
+
} if (typeof object === "object" && object != null) {
|
743
|
+
var newObject = {};
|
744
|
+
for (var key in object) {
|
745
|
+
if (object.hasOwnProperty(key)) {
|
746
|
+
newObject[key] = truncateDeep(object[key], length, newDepth);
|
747
|
+
}
|
748
|
+
}
|
749
|
+
return newObject;
|
750
|
+
|
751
|
+
// Just return everything else (numbers, booleans, functions, etc.)
|
752
|
+
} else {
|
753
|
+
return object;
|
754
|
+
}
|
755
|
+
}
|
756
|
+
|
757
|
+
// Deeply serialize an object into a query string. We use the PHP-style
|
758
|
+
// nested object syntax, `nested[keys]=val`, to support heirachical
|
759
|
+
// objects. Similar to jQuery's `$.param` method.
|
760
|
+
function serialize(obj, prefix, depth) {
|
761
|
+
var maxDepth = getSetting("maxDepth", maxPayloadDepth);
|
762
|
+
|
763
|
+
if (depth >= maxDepth) {
|
764
|
+
return encodeURIComponent(prefix) + "=[RECURSIVE]";
|
765
|
+
}
|
766
|
+
depth = depth + 1 || 1;
|
767
|
+
|
768
|
+
try {
|
769
|
+
if (window.Node && obj instanceof window.Node) {
|
770
|
+
return encodeURIComponent(prefix) + "=" + encodeURIComponent(targetToString(obj));
|
771
|
+
}
|
772
|
+
|
773
|
+
var str = [];
|
774
|
+
for (var p in obj) {
|
775
|
+
if (obj.hasOwnProperty(p) && p != null && obj[p] != null) {
|
776
|
+
var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p];
|
777
|
+
str.push(typeof v === "object" ? serialize(v, k, depth) : encodeURIComponent(k) + "=" + encodeURIComponent(v));
|
778
|
+
}
|
779
|
+
}
|
780
|
+
return str.sort().join("&");
|
781
|
+
} catch (e) {
|
782
|
+
return encodeURIComponent(prefix) + "=" + encodeURIComponent("" + e);
|
783
|
+
}
|
784
|
+
}
|
785
|
+
|
786
|
+
|
787
|
+
// Deep-merge the `source` object into the `target` object and return
|
788
|
+
// the `target`. Properties in source that will overwrite those in target.
|
789
|
+
// Similar to jQuery's `$.extend` method.
|
790
|
+
function merge(target, source, depth) {
|
791
|
+
if (source == null) {
|
792
|
+
return target;
|
793
|
+
} else if (depth >= getSetting("maxDepth", maxPayloadDepth)) {
|
794
|
+
return "[RECURSIVE]";
|
795
|
+
}
|
796
|
+
|
797
|
+
target = target || {};
|
798
|
+
for (var key in source) {
|
799
|
+
if (source.hasOwnProperty(key)) {
|
800
|
+
try {
|
801
|
+
if (source[key].constructor === Object) {
|
802
|
+
target[key] = merge(target[key], source[key], depth + 1 || 1);
|
803
|
+
} else {
|
804
|
+
target[key] = source[key];
|
805
|
+
}
|
806
|
+
} catch (e) {
|
807
|
+
target[key] = source[key];
|
808
|
+
}
|
809
|
+
}
|
810
|
+
}
|
811
|
+
|
812
|
+
return target;
|
813
|
+
}
|
814
|
+
|
815
|
+
// Make a HTTP request with given `url` and `params` object.
|
816
|
+
// For maximum browser compatibility and cross-domain support, requests are
|
817
|
+
// made by creating a temporary JavaScript `Image` object.
|
818
|
+
// Additionally the request can be done via XHR (needed for Chrome apps and extensions)
|
819
|
+
// To set the script to use XHR, you can specify data-notifyhandler attribute in the script tag
|
820
|
+
// Eg. `<script data-notifyhandler="xhr">` - the request defaults to image if attribute is not set
|
821
|
+
function request(url, params) {
|
822
|
+
url += "?" + serialize(params) + "&ct=img&cb=" + new Date().getTime();
|
823
|
+
if (typeof BUGSNAG_TESTING !== "undefined" && self.testRequest) {
|
824
|
+
self.testRequest(url, params);
|
825
|
+
} else {
|
826
|
+
var notifyHandler = getSetting("notifyHandler");
|
827
|
+
if (notifyHandler === "xhr") {
|
828
|
+
var xhr = new XMLHttpRequest();
|
829
|
+
xhr.open("GET", url, true);
|
830
|
+
xhr.send();
|
831
|
+
} else {
|
832
|
+
var img = new Image();
|
833
|
+
img.src = url;
|
834
|
+
}
|
835
|
+
}
|
836
|
+
}
|
837
|
+
|
838
|
+
// Extract all `data-*` attributes from a DOM element and return them as an
|
839
|
+
// object. This is used to allow Bugsnag settings to be set via attributes
|
840
|
+
// on the `script` tag, eg. `<script data-apikey="xyz">`.
|
841
|
+
// Similar to jQuery's `$(el).data()` method.
|
842
|
+
function getData(node) {
|
843
|
+
var dataAttrs = {};
|
844
|
+
var dataRegex = /^data\-([\w\-]+)$/;
|
845
|
+
|
846
|
+
// If the node doesn't exist due to being loaded as a commonjs module,
|
847
|
+
// then return an empty object and fallback to self[].
|
848
|
+
if (node) {
|
849
|
+
var attrs = node.attributes;
|
850
|
+
for (var i = 0; i < attrs.length; i++) {
|
851
|
+
var attr = attrs[i];
|
852
|
+
if (dataRegex.test(attr.nodeName)) {
|
853
|
+
var key = attr.nodeName.match(dataRegex)[1];
|
854
|
+
dataAttrs[key] = attr.value || attr.nodeValue;
|
855
|
+
}
|
856
|
+
}
|
857
|
+
}
|
858
|
+
|
859
|
+
return dataAttrs;
|
860
|
+
}
|
861
|
+
|
862
|
+
// Get configuration settings from either `self` (the `Bugsnag` object)
|
863
|
+
// or `data` (the `data-*` attributes).
|
864
|
+
var data;
|
865
|
+
function getSetting(name, fallback) {
|
866
|
+
data = data || getData(thisScript);
|
867
|
+
var setting = self[name] !== undefined ? self[name] : data[name.toLowerCase()];
|
868
|
+
if (setting === "false") {
|
869
|
+
setting = false;
|
870
|
+
}
|
871
|
+
return setting !== undefined ? setting : fallback;
|
872
|
+
}
|
873
|
+
|
874
|
+
// Validate a Bugsnag API key exists and is of the correct format.
|
875
|
+
function validateApiKey(apiKey) {
|
876
|
+
if (!apiKey || !apiKey.match(API_KEY_REGEX)) {
|
877
|
+
log("Invalid API key '" + apiKey + "'");
|
878
|
+
return false;
|
879
|
+
}
|
880
|
+
|
881
|
+
return true;
|
882
|
+
}
|
883
|
+
|
884
|
+
// get breadcrumb specific setting. When autoBreadcrumbs is true, all individual events are defaulted
|
885
|
+
// to true. Otherwise they will all default to false. You can set any event specifically and it will override
|
886
|
+
// the default.
|
887
|
+
function getBreadcrumbSetting(name) {
|
888
|
+
var fallback = getSetting("autoBreadcrumbs", true);
|
889
|
+
return getSetting(name, fallback);
|
890
|
+
}
|
891
|
+
|
892
|
+
// Send an error to Bugsnag.
|
893
|
+
function sendToBugsnag(details, metaData) {
|
894
|
+
// Validate the configured API key.
|
895
|
+
var apiKey = getSetting("apiKey");
|
896
|
+
if (!validateApiKey(apiKey) || !eventsRemaining) {
|
897
|
+
return;
|
898
|
+
}
|
899
|
+
|
900
|
+
eventsRemaining -= 1;
|
901
|
+
|
902
|
+
// Check if we should notify for this release stage.
|
903
|
+
var releaseStage = getSetting("releaseStage", "production");
|
904
|
+
var notifyReleaseStages = getSetting("notifyReleaseStages");
|
905
|
+
if (notifyReleaseStages) {
|
906
|
+
var shouldNotify = false;
|
907
|
+
for (var i = 0; i < notifyReleaseStages.length; i++) {
|
908
|
+
if (releaseStage === notifyReleaseStages[i]) {
|
909
|
+
shouldNotify = true;
|
910
|
+
break;
|
911
|
+
}
|
912
|
+
}
|
913
|
+
|
914
|
+
if (!shouldNotify) {
|
915
|
+
return;
|
916
|
+
}
|
917
|
+
}
|
918
|
+
|
919
|
+
// Don't send multiple copies of the same error.
|
920
|
+
// This fixes a problem when a client goes into an infinite loop,
|
921
|
+
// and starts wasting all their bandwidth sending messages to bugsnag.
|
922
|
+
var deduplicate = [details.name, details.message, details.stacktrace].join("|");
|
923
|
+
if (deduplicate === previousNotification) {
|
924
|
+
return;
|
925
|
+
} else {
|
926
|
+
previousNotification = deduplicate;
|
927
|
+
}
|
928
|
+
|
929
|
+
var defaultEventMetaData = {
|
930
|
+
device: { time: (new Date()).getTime() }
|
931
|
+
};
|
932
|
+
|
933
|
+
// Build the request payload by combining error information with other data
|
934
|
+
// such as user-agent and locale, `metaData` and settings.
|
935
|
+
var payload = {
|
936
|
+
notifierVersion: NOTIFIER_VERSION,
|
937
|
+
|
938
|
+
apiKey: apiKey,
|
939
|
+
projectRoot: getSetting("projectRoot") || window.location.protocol + "//" + window.location.host,
|
940
|
+
context: getSetting("context") || window.location.pathname,
|
941
|
+
user: getSetting("user"),
|
942
|
+
metaData: merge(merge(defaultEventMetaData, getSetting("metaData")), metaData),
|
943
|
+
releaseStage: releaseStage,
|
944
|
+
appVersion: getSetting("appVersion"),
|
945
|
+
|
946
|
+
url: window.location.href,
|
947
|
+
userAgent: navigator.userAgent,
|
948
|
+
language: navigator.language || navigator.userLanguage,
|
949
|
+
|
950
|
+
severity: details.severity,
|
951
|
+
|
952
|
+
name: details.name,
|
953
|
+
message: details.message,
|
954
|
+
stacktrace: details.stacktrace,
|
955
|
+
file: details.file,
|
956
|
+
lineNumber: details.lineNumber,
|
957
|
+
columnNumber: details.columnNumber,
|
958
|
+
breadcrumbs: breadcrumbs,
|
959
|
+
payloadVersion: "3"
|
960
|
+
};
|
961
|
+
|
962
|
+
// Run any `beforeNotify` function
|
963
|
+
var beforeNotify = self.beforeNotify;
|
964
|
+
if (typeof(beforeNotify) === "function") {
|
965
|
+
var retVal = beforeNotify(payload, payload.metaData);
|
966
|
+
if (retVal === false) {
|
967
|
+
return;
|
968
|
+
}
|
969
|
+
}
|
970
|
+
|
971
|
+
if (payload.lineNumber === 0 && (/Script error\.?/).test(payload.message)) {
|
972
|
+
return log("Ignoring cross-domain or eval script error. See https://docs.bugsnag.com/platforms/browsers/faq/#3-cross-origin-script-errors");
|
973
|
+
}
|
974
|
+
|
975
|
+
// Make the HTTP request
|
976
|
+
request(getSetting("endpoint") || DEFAULT_NOTIFIER_ENDPOINT, payload);
|
977
|
+
}
|
978
|
+
|
979
|
+
// Generate a browser stacktrace (or approximation) from the current stack.
|
980
|
+
// This is used to add a stacktrace to `Bugsnag.notify` calls, and to add a
|
981
|
+
// stacktrace approximation where we can't get one from an exception.
|
982
|
+
function generateStacktrace() {
|
983
|
+
var generated, stacktrace;
|
984
|
+
var MAX_FAKE_STACK_SIZE = 10;
|
985
|
+
var ANONYMOUS_FUNCTION_PLACEHOLDER = "[anonymous]";
|
986
|
+
|
987
|
+
// Try to generate a real stacktrace (most browsers, except IE9 and below).
|
988
|
+
try {
|
989
|
+
throw new Error("");
|
990
|
+
} catch (exception) {
|
991
|
+
generated = "<generated>\n";
|
992
|
+
stacktrace = stacktraceFromException(exception);
|
993
|
+
}
|
994
|
+
|
995
|
+
// Otherwise, build a fake stacktrace from the list of method names by
|
996
|
+
// looping through the list of functions that called this one (and skip
|
997
|
+
// whoever called us).
|
998
|
+
if (!stacktrace) {
|
999
|
+
generated = "<generated-ie>\n";
|
1000
|
+
var functionStack = [];
|
1001
|
+
try {
|
1002
|
+
var curr = arguments.callee.caller.caller;
|
1003
|
+
while (curr && functionStack.length < MAX_FAKE_STACK_SIZE) {
|
1004
|
+
var fn = FUNCTION_REGEX.test(curr.toString()) ? RegExp.$1 || ANONYMOUS_FUNCTION_PLACEHOLDER : ANONYMOUS_FUNCTION_PLACEHOLDER;
|
1005
|
+
functionStack.push(fn);
|
1006
|
+
curr = curr.caller;
|
1007
|
+
}
|
1008
|
+
} catch (e) {
|
1009
|
+
log(e);
|
1010
|
+
}
|
1011
|
+
stacktrace = functionStack.join("\n");
|
1012
|
+
}
|
1013
|
+
|
1014
|
+
// Tell the backend to ignore the first two lines in the stack-trace.
|
1015
|
+
// generateStacktrace() + window.onerror,
|
1016
|
+
// generateStacktrace() + notify,
|
1017
|
+
// generateStacktrace() + notifyException
|
1018
|
+
return generated + stacktrace;
|
1019
|
+
}
|
1020
|
+
|
1021
|
+
// Get the stacktrace string from an exception
|
1022
|
+
function stacktraceFromException(exception) {
|
1023
|
+
return exception.stack || exception.backtrace || exception.stacktrace;
|
1024
|
+
}
|
1025
|
+
|
1026
|
+
// Convert a DOM element into a string suitable for passing to Bugsnag.
|
1027
|
+
function targetToString(target) {
|
1028
|
+
if (target) {
|
1029
|
+
var attrs = target.attributes;
|
1030
|
+
|
1031
|
+
if (attrs) {
|
1032
|
+
var ret = "<" + target.nodeName.toLowerCase();
|
1033
|
+
for (var i = 0; i < attrs.length; i++) {
|
1034
|
+
if (attrs[i].value && attrs[i].value.toString() !== "null") {
|
1035
|
+
ret += " " + attrs[i].name + "=\"" + attrs[i].value + "\"";
|
1036
|
+
}
|
1037
|
+
}
|
1038
|
+
return ret + ">";
|
1039
|
+
} else {
|
1040
|
+
// e.g. #document
|
1041
|
+
return target.nodeName;
|
1042
|
+
}
|
1043
|
+
}
|
1044
|
+
}
|
1045
|
+
|
1046
|
+
// If we've notified bugsnag of an exception in wrap, we don't want to
|
1047
|
+
// re-notify when it hits window.onerror after we re-throw it.
|
1048
|
+
function ignoreNextOnError() {
|
1049
|
+
ignoreOnError += 1;
|
1050
|
+
window.setTimeout(function () {
|
1051
|
+
ignoreOnError -= 1;
|
1052
|
+
});
|
1053
|
+
}
|
1054
|
+
|
1055
|
+
// Disable catching on IE < 10 as it destroys stack-traces from generateStackTrace()
|
1056
|
+
if (!window.atob) {
|
1057
|
+
shouldCatch = false;
|
1058
|
+
|
1059
|
+
// Disable catching on browsers that support HTML5 ErrorEvents properly.
|
1060
|
+
// This lets debug on unhandled exceptions work.
|
1061
|
+
} else if (window.ErrorEvent) {
|
1062
|
+
try {
|
1063
|
+
if (new window.ErrorEvent("test").colno === 0) {
|
1064
|
+
shouldCatch = false;
|
1065
|
+
}
|
1066
|
+
} catch(e){ /* No action needed */ }
|
1067
|
+
}
|
1068
|
+
|
1069
|
+
|
1070
|
+
//
|
1071
|
+
// ### Polyfilling
|
1072
|
+
//
|
1073
|
+
|
1074
|
+
// Add a polyFill to an object
|
1075
|
+
function polyFill(obj, name, makeReplacement) {
|
1076
|
+
var original = obj[name];
|
1077
|
+
var replacement = makeReplacement(original);
|
1078
|
+
obj[name] = replacement;
|
1079
|
+
|
1080
|
+
if (typeof BUGSNAG_TESTING !== "undefined" && window.undo) {
|
1081
|
+
window.undo.push(function () {
|
1082
|
+
obj[name] = original;
|
1083
|
+
});
|
1084
|
+
}
|
1085
|
+
}
|
1086
|
+
|
1087
|
+
if (getSetting("autoNotify", true)) {
|
1088
|
+
//
|
1089
|
+
// ### Automatic error notification
|
1090
|
+
//
|
1091
|
+
// Attach to `window.onerror` events and notify Bugsnag when they happen.
|
1092
|
+
// These are mostly js compile/parse errors, but on some browsers all
|
1093
|
+
// "uncaught" exceptions will fire this event.
|
1094
|
+
//
|
1095
|
+
polyFill(window, "onerror", function (_super) {
|
1096
|
+
// Keep a reference to any existing `window.onerror` handler
|
1097
|
+
if (typeof BUGSNAG_TESTING !== "undefined") {
|
1098
|
+
self._onerror = _super;
|
1099
|
+
}
|
1100
|
+
|
1101
|
+
return function bugsnag(message, url, lineNo, charNo, exception) {
|
1102
|
+
var shouldNotify = getSetting("autoNotify", true);
|
1103
|
+
var metaData = {};
|
1104
|
+
|
1105
|
+
// IE 6+ support.
|
1106
|
+
if (!charNo && window.event) {
|
1107
|
+
charNo = window.event.errorCharacter;
|
1108
|
+
}
|
1109
|
+
|
1110
|
+
addScriptToMetaData(metaData);
|
1111
|
+
lastScript = null;
|
1112
|
+
|
1113
|
+
if (shouldNotify && !ignoreOnError) {
|
1114
|
+
var name = exception && exception.name || "window.onerror";
|
1115
|
+
sendToBugsnag({
|
1116
|
+
name: name,
|
1117
|
+
message: message,
|
1118
|
+
file: url,
|
1119
|
+
lineNumber: lineNo,
|
1120
|
+
columnNumber: charNo,
|
1121
|
+
stacktrace: (exception && stacktraceFromException(exception)) || generateStacktrace(),
|
1122
|
+
severity: "error"
|
1123
|
+
}, metaData);
|
1124
|
+
|
1125
|
+
// add the error to the breadcrumbs
|
1126
|
+
if (getBreadcrumbSetting("autoBreadcrumbsErrors")) {
|
1127
|
+
self.leaveBreadcrumb({
|
1128
|
+
type: "error",
|
1129
|
+
name: name,
|
1130
|
+
metaData: {
|
1131
|
+
severity: "error",
|
1132
|
+
file: url,
|
1133
|
+
message: message,
|
1134
|
+
line: lineNo
|
1135
|
+
}
|
1136
|
+
});
|
1137
|
+
}
|
1138
|
+
}
|
1139
|
+
|
1140
|
+
if (typeof BUGSNAG_TESTING !== "undefined") {
|
1141
|
+
_super = self._onerror;
|
1142
|
+
}
|
1143
|
+
|
1144
|
+
// Fire the existing `window.onerror` handler, if one exists
|
1145
|
+
if (_super) {
|
1146
|
+
_super(message, url, lineNo, charNo, exception);
|
1147
|
+
}
|
1148
|
+
};
|
1149
|
+
});
|
1150
|
+
|
1151
|
+
var hijackTimeFunc = function (_super) {
|
1152
|
+
// Note, we don't do `_super.call` because that doesn't work on IE 8,
|
1153
|
+
// luckily this is implicitly window so it just works everywhere.
|
1154
|
+
//
|
1155
|
+
// setTimout in all browsers except IE <9 allows additional parameters
|
1156
|
+
// to be passed, so in order to support these without resorting to call/apply
|
1157
|
+
// we need an extra layer of wrapping.
|
1158
|
+
return function (f, t) {
|
1159
|
+
if (typeof f === "function") {
|
1160
|
+
f = wrap(f);
|
1161
|
+
var args = Array.prototype.slice.call(arguments, 2);
|
1162
|
+
return _super(function () {
|
1163
|
+
f.apply(this, args);
|
1164
|
+
}, t);
|
1165
|
+
} else {
|
1166
|
+
return _super(f, t);
|
1167
|
+
}
|
1168
|
+
};
|
1169
|
+
};
|
1170
|
+
|
1171
|
+
polyFill(window, "setTimeout", hijackTimeFunc);
|
1172
|
+
polyFill(window, "setInterval", hijackTimeFunc);
|
1173
|
+
|
1174
|
+
if (window.requestAnimationFrame) {
|
1175
|
+
polyFill(window, "requestAnimationFrame", function (_super) {
|
1176
|
+
return function (callback) {
|
1177
|
+
return _super(wrap(callback));
|
1178
|
+
};
|
1179
|
+
});
|
1180
|
+
}
|
1181
|
+
|
1182
|
+
if (window.setImmediate) {
|
1183
|
+
polyFill(window, "setImmediate", function (_super) {
|
1184
|
+
return function () {
|
1185
|
+
var args = Array.prototype.slice.call(arguments);
|
1186
|
+
args[0] = wrap(args[0]);
|
1187
|
+
return _super.apply(this, args);
|
1188
|
+
};
|
1189
|
+
});
|
1190
|
+
}
|
1191
|
+
|
1192
|
+
// EventTarget is all that's required in modern chrome/opera
|
1193
|
+
// EventTarget + Window + ModalWindow is all that's required in modern FF (there are a few Moz prefixed ones that we're ignoring)
|
1194
|
+
// The rest is a collection of stuff for Safari and IE 11. (Again ignoring a few MS and WebKit prefixed things)
|
1195
|
+
"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) {
|
1196
|
+
var prototype = window[global] && window[global].prototype;
|
1197
|
+
if (prototype && prototype.hasOwnProperty && prototype.hasOwnProperty("addEventListener")) {
|
1198
|
+
polyFill(prototype, "addEventListener", function (_super) {
|
1199
|
+
return function (e, f, capture, secure) {
|
1200
|
+
// HTML lets event-handlers be objects with a handlEvent function,
|
1201
|
+
// we need to change f.handleEvent here, as self.wrap will ignore f.
|
1202
|
+
try {
|
1203
|
+
if (f && f.handleEvent) {
|
1204
|
+
f.handleEvent = wrap(f.handleEvent, {eventHandler: true});
|
1205
|
+
}
|
1206
|
+
} catch (err) {
|
1207
|
+
// When selenium is installed, we sometimes get 'Permission denied to access property "handleEvent"'
|
1208
|
+
// Because this catch is around Bugsnag library code, it won't catch any user errors
|
1209
|
+
log(err);
|
1210
|
+
}
|
1211
|
+
return _super.call(this, e, wrap(f, {eventHandler: true}), capture, secure);
|
1212
|
+
};
|
1213
|
+
});
|
1214
|
+
|
1215
|
+
// We also need to hack removeEventListener so that you can remove any
|
1216
|
+
// event listeners.
|
1217
|
+
polyFill(prototype, "removeEventListener", function (_super) {
|
1218
|
+
return function (e, f, capture, secure) {
|
1219
|
+
_super.call(this, e, f, capture, secure);
|
1220
|
+
return _super.call(this, e, wrap(f), capture, secure);
|
1221
|
+
};
|
1222
|
+
});
|
1223
|
+
}
|
1224
|
+
});
|
1225
|
+
}
|
1226
|
+
|
1227
|
+
// setup auto breadcrumb tracking
|
1228
|
+
setupClickBreadcrumbs();
|
1229
|
+
setupConsoleBreadcrumbs();
|
1230
|
+
setupNavigationBreadcrumbs();
|
1231
|
+
|
1232
|
+
// Leave the initial breadcrumb
|
1233
|
+
if (getSetting("autoBreadcrumbs", true)) {
|
1234
|
+
self.leaveBreadcrumb({ type: "navigation", name: "Bugsnag Loaded" });
|
1235
|
+
}
|
1236
|
+
|
1237
|
+
window.Bugsnag = self;
|
1238
|
+
// If people are using a javascript loader, we should integrate with it.
|
1239
|
+
// We don't want to defer instrumenting their code with callbacks however,
|
1240
|
+
// so we slightly abuse the intent and continue with our plan of polyfilling
|
1241
|
+
// the browser whether or not they ever actually require the module.
|
1242
|
+
// This has the nice side-effect of continuing to work when people are using
|
1243
|
+
// AMD but loading Bugsnag via a CDN.
|
1244
|
+
// It has the not-so-nice side-effect of polluting the global namespace, but
|
1245
|
+
// you can easily call Bugsnag.noConflict() to fix that.
|
1246
|
+
if (typeof define === "function" && define.amd) {
|
1247
|
+
// AMD
|
1248
|
+
define([], function () {
|
1249
|
+
return self;
|
1250
|
+
});
|
1251
|
+
} else if (typeof module === "object" && typeof module.exports === "object") {
|
1252
|
+
// CommonJS/Browserify
|
1253
|
+
module.exports = self;
|
1254
|
+
}
|
1255
|
+
})(window, window.Bugsnag);
|
1256
|
+
|