1 |
|
2 | (function (global, factory) {
|
3 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
4 | typeof define === 'function' && define.amd ? define(factory) :
|
5 | (global = global || self, global.showJSError = factory());
|
6 | }(this, function () { 'use strict';
|
7 |
|
8 | var showJSError = {
|
9 | |
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | init: function(settings) {
|
22 | if (this._inited) {
|
23 | return;
|
24 | }
|
25 |
|
26 | var that = this,
|
27 | isAndroidOrIOS = /(Android|iPhone|iPod|iPad)/i.test(navigator.userAgent);
|
28 |
|
29 | this.settings = settings || {};
|
30 |
|
31 | this._inited = true;
|
32 | this._isLast = true;
|
33 | this._i = 0;
|
34 | this._buffer = [];
|
35 |
|
36 | this._onerror = function(e) {
|
37 | if (isAndroidOrIOS && e && e.message === 'Script error.' && !e.lineno && !e.filename) {
|
38 | return;
|
39 | }
|
40 |
|
41 | that._buffer.push(e);
|
42 | if (that._isLast) {
|
43 | that._i = that._buffer.length - 1;
|
44 | }
|
45 |
|
46 | that._update();
|
47 | };
|
48 |
|
49 | if (window.addEventListener) {
|
50 | window.addEventListener('error', this._onerror, false);
|
51 | } else {
|
52 | this._oldOnError = window.onerror;
|
53 |
|
54 | window.onerror = function(message, filename, lineno, colno, error) {
|
55 | that._onerror({
|
56 | message: message,
|
57 | filename: filename,
|
58 | lineno: lineno,
|
59 | colno: colno,
|
60 | error: error
|
61 | });
|
62 |
|
63 | if (typeof that._oldOnError === 'function') {
|
64 | that._oldOnError.apply(window, arguments);
|
65 | }
|
66 | };
|
67 | }
|
68 | },
|
69 | |
70 |
|
71 |
|
72 | destruct: function() {
|
73 | if (!this._inited) { return; }
|
74 |
|
75 | if (window.addEventListener) {
|
76 | window.removeEventListener('error', this._onerror, false);
|
77 | } else {
|
78 | window.onerror = this._oldOnError || null;
|
79 | delete this._oldOnError;
|
80 | }
|
81 |
|
82 | if (document.body && this._container) {
|
83 | document.body.removeChild(this._container);
|
84 | }
|
85 |
|
86 | this._buffer = [];
|
87 |
|
88 | this._inited = false;
|
89 | },
|
90 | |
91 |
|
92 |
|
93 |
|
94 |
|
95 | show: function(err) {
|
96 | if (typeof err !== 'undefined') {
|
97 | this._buffer.push(typeof err === 'object' ? err : new Error(err));
|
98 | }
|
99 |
|
100 | this._update();
|
101 | this._show();
|
102 | },
|
103 | |
104 |
|
105 |
|
106 | hide: function() {
|
107 | if (this._container) {
|
108 | this._container.className = this.elemClass('');
|
109 | }
|
110 | },
|
111 | |
112 |
|
113 |
|
114 | copyText: function() {
|
115 | var err = this._buffer[this._i],
|
116 | text = this._getDetailedMessage(err),
|
117 | body = document.body,
|
118 | textarea = this.elem({
|
119 | name: 'textarea',
|
120 | tag: 'textarea',
|
121 | props: {
|
122 | innerHTML: text
|
123 | },
|
124 | container: body
|
125 | });
|
126 |
|
127 | try {
|
128 | textarea.select();
|
129 | document.execCommand('copy');
|
130 | } catch (e) {
|
131 | alert('Copying text is not supported in this browser.');
|
132 | }
|
133 |
|
134 | body.removeChild(textarea);
|
135 | },
|
136 | |
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 | elem: function(data) {
|
147 | var el = document.createElement(data.tag || 'div'),
|
148 | props = data.props;
|
149 |
|
150 | for (var i in props) {
|
151 | if (props.hasOwnProperty(i)) {
|
152 | el[i] = props[i];
|
153 | }
|
154 | }
|
155 |
|
156 | el.className = this.elemClass(data.name);
|
157 |
|
158 | data.container.appendChild(el);
|
159 |
|
160 | return el;
|
161 | },
|
162 | |
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 | elemClass: function(name, mod) {
|
170 | var cl = 'show-js-error';
|
171 | if (name) {
|
172 | cl += '__' + name;
|
173 | }
|
174 |
|
175 | if (mod) {
|
176 | cl += ' ' + cl + '_' + mod;
|
177 | }
|
178 |
|
179 | return cl;
|
180 | },
|
181 | |
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 | escapeHTML: function(text) {
|
188 | return (text || '').replace(/[&<>"'/]/g, function(sym) {
|
189 | return {
|
190 | '&': '&',
|
191 | '<': '<',
|
192 | '>': '>',
|
193 | '"': '"',
|
194 | '\'': ''',
|
195 | '/': '/'
|
196 | }[sym];
|
197 | });
|
198 | },
|
199 | |
200 |
|
201 |
|
202 | toggleDetailed: function() {
|
203 | var body = this._body;
|
204 | if (body) {
|
205 | if (this._toggleDetailed) {
|
206 | this._toggleDetailed = false;
|
207 | body.className = this.elemClass('body');
|
208 | } else {
|
209 | this._toggleDetailed = true;
|
210 | body.className = this.elemClass('body', 'detailed');
|
211 | }
|
212 | }
|
213 | },
|
214 | _append: function() {
|
215 | var that = this;
|
216 |
|
217 | this._container = document.createElement('div');
|
218 | this._container.className = this.elemClass('');
|
219 |
|
220 | this._title = this.elem({
|
221 | name: 'title',
|
222 | props: {
|
223 | innerHTML: this._getTitle()
|
224 | },
|
225 | container: this._container
|
226 | });
|
227 |
|
228 | this._body = this.elem({
|
229 | name: 'body',
|
230 | container: this._container
|
231 | });
|
232 |
|
233 | this._message = this.elem({
|
234 | name: 'message',
|
235 | props: {
|
236 | onclick: function() {
|
237 | that.toggleDetailed();
|
238 | }
|
239 | },
|
240 | container: this._body
|
241 | });
|
242 |
|
243 | if (this.settings.helpLinks) {
|
244 | this._helpLinks = this.elem({
|
245 | name: 'help',
|
246 | container: this._body
|
247 | });
|
248 |
|
249 | this._mdn = this.elem({
|
250 | tag: 'a',
|
251 | name: 'mdn',
|
252 | props: {
|
253 | target: '_blank',
|
254 | innerHTML: 'MDN'
|
255 | },
|
256 | container: this._helpLinks
|
257 | });
|
258 |
|
259 | this._stackoverflow = this.elem({
|
260 | tag: 'a',
|
261 | name: 'stackoverflow',
|
262 | props: {
|
263 | target: '_blank',
|
264 | innerHTML: 'Stack Overflow'
|
265 | },
|
266 | container: this._helpLinks
|
267 | });
|
268 | }
|
269 |
|
270 | this._filename = this.elem({
|
271 | name: 'filename',
|
272 | container: this._body
|
273 | });
|
274 |
|
275 | if (this.settings.userAgent) {
|
276 | this._ua = this.elem({
|
277 | name: 'ua',
|
278 | container: this._body
|
279 | });
|
280 | }
|
281 |
|
282 | if (this.settings.additionalText) {
|
283 | this._additionalText = this.elem({
|
284 | name: 'additional-text',
|
285 | container: this._body
|
286 | });
|
287 | }
|
288 |
|
289 | this.elem({
|
290 | name: 'close',
|
291 | props: {
|
292 | innerHTML: '×',
|
293 | onclick: function() {
|
294 | that.hide();
|
295 | }
|
296 | },
|
297 | container: this._container
|
298 | });
|
299 |
|
300 | this._actions = this.elem({
|
301 | name: 'actions',
|
302 | container: this._container
|
303 | });
|
304 |
|
305 | this.elem({
|
306 | tag: 'input',
|
307 | name: 'copy',
|
308 | props: {
|
309 | type: 'button',
|
310 | value: this.settings.copyText || 'Copy',
|
311 | onclick: function() {
|
312 | that.copyText();
|
313 | }
|
314 | },
|
315 | container: this._actions
|
316 | });
|
317 |
|
318 | if (this.settings.sendUrl) {
|
319 | this._sendLink = this.elem({
|
320 | tag: 'a',
|
321 | name: 'send-link',
|
322 | props: {
|
323 | href: '',
|
324 | target: '_blank'
|
325 | },
|
326 | container: this._actions
|
327 | });
|
328 |
|
329 | this._send = this.elem({
|
330 | tag: 'input',
|
331 | name: 'send',
|
332 | props: {
|
333 | type: 'button',
|
334 | value: this.settings.sendText || 'Send'
|
335 | },
|
336 | container: this._sendLink
|
337 | });
|
338 | }
|
339 |
|
340 | this._arrows = this.elem({
|
341 | tag: 'span',
|
342 | name: 'arrows',
|
343 | container: this._actions
|
344 | });
|
345 |
|
346 | this._prev = this.elem({
|
347 | tag: 'input',
|
348 | name: 'prev',
|
349 | props: {
|
350 | type: 'button',
|
351 | value: '←',
|
352 | onclick: function() {
|
353 | that._isLast = false;
|
354 | if (that._i) {
|
355 | that._i--;
|
356 | }
|
357 |
|
358 | that._update();
|
359 | }
|
360 | },
|
361 | container: this._arrows
|
362 | });
|
363 |
|
364 | this._next = this.elem({
|
365 | tag: 'input',
|
366 | name: 'next',
|
367 | props: {
|
368 | type: 'button',
|
369 | value: '→',
|
370 | onclick: function() {
|
371 | that._isLast = false;
|
372 | if (that._i < that._buffer.length - 1) {
|
373 | that._i++;
|
374 | }
|
375 |
|
376 | that._update();
|
377 | }
|
378 | },
|
379 | container: this._arrows
|
380 | });
|
381 |
|
382 | this._num = this.elem({
|
383 | tag: 'span',
|
384 | name: 'num',
|
385 | props: {
|
386 | innerHTML: this._i + 1
|
387 | },
|
388 | container: this._arrows
|
389 | });
|
390 |
|
391 | var append = function() {
|
392 | document.body.appendChild(that._container);
|
393 | };
|
394 |
|
395 | if (document.body) {
|
396 | append();
|
397 | } else {
|
398 | if (document.addEventListener) {
|
399 | document.addEventListener('DOMContentLoaded', append, false);
|
400 | } else if (document.attachEvent) {
|
401 | document.attachEvent('onload', append);
|
402 | }
|
403 | }
|
404 | },
|
405 | _getDetailedMessage: function(err) {
|
406 | var settings = this.settings,
|
407 | screen = typeof window.screen === 'object' ? window.screen : {},
|
408 | orientation = screen.orientation || screen.mozOrientation || screen.msOrientation || '',
|
409 | props = [
|
410 | ['Title', err.title || this._getTitle()],
|
411 | ['Message', this._getMessage(err)],
|
412 | ['Filename', this._getFilenameWithPosition(err)],
|
413 | ['Stack', this._getStack(err)],
|
414 | ['Page url', window.location.href],
|
415 | ['Refferer', document.referrer],
|
416 | ['User-agent', settings.userAgent || navigator.userAgent],
|
417 | ['Screen size', [screen.width, screen.height, screen.colorDepth].join('×')],
|
418 | ['Screen orientation', typeof orientation === 'string' ? orientation : orientation.type],
|
419 | ['Cookie enabled', navigator.cookieEnabled]
|
420 | ];
|
421 |
|
422 | var text = '';
|
423 | for (var i = 0; i < props.length; i++) {
|
424 | var item = props[i];
|
425 | text += item[0] + ': ' + item[1] + '\n';
|
426 | }
|
427 |
|
428 | if (settings.templateDetailedMessage) {
|
429 | text = settings.templateDetailedMessage.replace(/\{message\}/, text);
|
430 | }
|
431 |
|
432 | return text;
|
433 | },
|
434 | _getExtFilename: function(e) {
|
435 | var filename = e.filename,
|
436 | html = this.escapeHTML(this._getFilenameWithPosition(e));
|
437 |
|
438 | if (filename && filename.search(/^(https?|file):/) > -1) {
|
439 | return '<a target="_blank" href="' +
|
440 | this.escapeHTML(filename) + '">' + html + '</a>';
|
441 | } else {
|
442 | return html;
|
443 | }
|
444 | },
|
445 | _get: function(value, defaultValue) {
|
446 | return typeof value !== 'undefined' ? value : defaultValue;
|
447 | },
|
448 | _getFilenameWithPosition: function(e) {
|
449 | var text = e.filename || '';
|
450 | if (typeof e.lineno !== 'undefined') {
|
451 | text += ':' + this._get(e.lineno, '');
|
452 | if (typeof e.colno !== 'undefined') {
|
453 | text += ':' + this._get(e.colno, '');
|
454 | }
|
455 | }
|
456 |
|
457 | return text;
|
458 | },
|
459 | _getMessage: function(e) {
|
460 | var msg = e.message;
|
461 |
|
462 |
|
463 | if (e.error && e.error.name && 'number' in e.error) {
|
464 | msg = e.error.name + ': ' + msg;
|
465 | }
|
466 |
|
467 | return msg;
|
468 | },
|
469 | _getStack: function(err) {
|
470 | return (err.error && err.error.stack) || err.stack || '';
|
471 | },
|
472 | _getTitle: function() {
|
473 | return this.settings.title || 'JavaScript error';
|
474 | },
|
475 | _show: function() {
|
476 | this._container.className = this.elemClass('', 'visible');
|
477 | },
|
478 | _highlightLinks: function(text) {
|
479 | return text.replace(/(at | \(|@)(https?|file)(:.*?)(?=:\d+:\d+\)?$)/gm, function($0, $1, $2, $3) {
|
480 | var url = $2 + $3;
|
481 |
|
482 | return $1 + '<a target="_blank" href="' + url + '">' + url + '</a>';
|
483 | });
|
484 | },
|
485 | _update: function() {
|
486 | if (!this._appended) {
|
487 | this._append();
|
488 | this._appended = true;
|
489 | }
|
490 |
|
491 | var e = this._buffer[this._i],
|
492 | stack = this._getStack(e),
|
493 | filename;
|
494 |
|
495 | if (stack) {
|
496 | filename = this._highlightLinks(this.escapeHTML(stack));
|
497 | } else {
|
498 | filename = this._getExtFilename(e);
|
499 | }
|
500 |
|
501 | this._title.innerHTML = this.escapeHTML(e.title || this._getTitle());
|
502 |
|
503 | this._message.innerHTML = this.escapeHTML(this._getMessage(e));
|
504 |
|
505 | this._filename.innerHTML = filename;
|
506 |
|
507 | if (this._ua) {
|
508 | this._ua.innerHTML = this.escapeHTML(this.settings.userAgent);
|
509 | }
|
510 |
|
511 | if (this._additionalText) {
|
512 | this._additionalText.innerHTML = this.escapeHTML(this.settings.additionalText);
|
513 | }
|
514 |
|
515 | if (this._sendLink) {
|
516 | this._sendLink.href = this.settings.sendUrl
|
517 | .replace(/\{title\}/, encodeURIComponent(this._getMessage(e)))
|
518 | .replace(/\{body\}/, encodeURIComponent(this._getDetailedMessage(e)));
|
519 | }
|
520 |
|
521 | if (this._buffer.length > 1) {
|
522 | this._arrows.className = this.elemClass('arrows', 'visible');
|
523 | }
|
524 |
|
525 | if (this._helpLinks) {
|
526 | this._mdn.href = 'https://developer.mozilla.org/en-US/search?q=' + encodeURIComponent(e.message || e.stack || '');
|
527 | this._stackoverflow.href = 'https://stackoverflow.com/search?q=' + encodeURIComponent('[js] ' + (e.message || e.stack || ''));
|
528 | }
|
529 |
|
530 | this._prev.disabled = !this._i;
|
531 | this._num.innerHTML = (this._i + 1) + ' / ' + this._buffer.length;
|
532 | this._next.disabled = this._i === this._buffer.length - 1;
|
533 |
|
534 | this._show();
|
535 | }
|
536 | };
|
537 |
|
538 | return showJSError;
|
539 |
|
540 | }));
|