1 | (function (global, factory) {
|
2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
3 | typeof define === 'function' && define.amd ? define(factory) :
|
4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.showJSError = factory());
|
5 | })(this, (function () { 'use strict';
|
6 |
|
7 | function getScreenSize() {
|
8 | return [screen.width, screen.height, screen.colorDepth].join('×');
|
9 | }
|
10 | function getScreenOrientation() {
|
11 | return typeof screen.orientation === 'string' ? screen.orientation : screen.orientation.type;
|
12 | }
|
13 | function copyTextToClipboard(text) {
|
14 | var textarea = document.createElement('textarea');
|
15 | textarea.value = text;
|
16 | document.body.appendChild(textarea);
|
17 | try {
|
18 | textarea.select();
|
19 | document.execCommand('copy');
|
20 | }
|
21 | catch (e) {
|
22 | alert('Copying text is not supported in this browser.');
|
23 | }
|
24 | document.body.removeChild(textarea);
|
25 | }
|
26 |
|
27 | function createElem(data) {
|
28 | var elem = document.createElement(data.tag || 'div');
|
29 | if (data.props) {
|
30 | addProps(elem, data.props);
|
31 | }
|
32 | elem.className = buildElemClass(data.name);
|
33 | data.container.appendChild(elem);
|
34 | return elem;
|
35 | }
|
36 | function addProps(elem, props) {
|
37 | Object.keys(props).forEach(function (key) {
|
38 | elem[key] = props[key];
|
39 | });
|
40 | }
|
41 | function buildElemClass(name, mod) {
|
42 | var elemName = 'show-js-error';
|
43 | if (name) {
|
44 | elemName += '__' + name;
|
45 | }
|
46 | var className = elemName;
|
47 | if (mod) {
|
48 | Object.keys(mod).forEach(function (modName) {
|
49 | var modValue = mod[modName];
|
50 | if (modValue === false || modValue === null || modValue === undefined || modValue === '') {
|
51 | return;
|
52 | }
|
53 | if (mod[modName] === true) {
|
54 | className += ' ' + elemName + '_' + modName;
|
55 | }
|
56 | else {
|
57 | className += ' ' + elemName + '_' + modName + '_' + modValue;
|
58 | }
|
59 | });
|
60 | }
|
61 | return className;
|
62 | }
|
63 |
|
64 | function getStack(error) {
|
65 | return error && error.stack || '';
|
66 | }
|
67 | function getMessage(error) {
|
68 | return error && error.message || '';
|
69 | }
|
70 | function getValue(value, defaultValue) {
|
71 | return typeof value === 'undefined' ? defaultValue : value;
|
72 | }
|
73 | function getFilenameWithPosition(error) {
|
74 | if (!error) {
|
75 | return '';
|
76 | }
|
77 | var text = error.filename || '';
|
78 | if (typeof error.lineno !== 'undefined') {
|
79 | text += ':' + getValue(error.lineno, '');
|
80 | if (typeof error.colno !== 'undefined') {
|
81 | text += ':' + getValue(error.colno, '');
|
82 | }
|
83 | }
|
84 | return text;
|
85 | }
|
86 |
|
87 | var ShowJSError = (function () {
|
88 | function ShowJSError() {
|
89 | var _this = this;
|
90 | this.elems = {};
|
91 | this.state = {
|
92 | appended: false,
|
93 | detailed: false,
|
94 | errorIndex: 0,
|
95 | errorBuffer: [],
|
96 | };
|
97 | this.onerror = function (event) {
|
98 | var error = event.error;
|
99 | _this.pushError({
|
100 | title: 'JavaScript Error',
|
101 | message: error.message,
|
102 | filename: error.filename,
|
103 | colno: error.colno,
|
104 | lineno: error.lineno,
|
105 | stack: error.stack,
|
106 | });
|
107 | };
|
108 | this.onsecuritypolicyviolation = function (error) {
|
109 | _this.pushError({
|
110 | title: 'CSP Error',
|
111 | message: "blockedURI: ".concat(error.blockedURI || '', "\n violatedDirective: ").concat(error.violatedDirective, " || ''\n originalPolicy: ").concat(error.originalPolicy || ''),
|
112 | colno: error.columnNumber,
|
113 | filename: error.sourceFile,
|
114 | lineno: error.lineNumber,
|
115 | });
|
116 | };
|
117 | this.onunhandledrejection = function (error) {
|
118 | _this.pushError({
|
119 | title: 'Unhandled promise rejection',
|
120 | message: error.reason.message,
|
121 | colno: error.reason.colno,
|
122 | filename: error.reason.filename,
|
123 | lineno: error.reason.lineno,
|
124 | stack: error.reason.stack,
|
125 | });
|
126 | };
|
127 | this.appendToBody = function () {
|
128 | document.removeEventListener('DOMContentLoaded', _this.appendToBody, false);
|
129 | if (_this.elems.container) {
|
130 | document.body.appendChild(_this.elems.container);
|
131 | }
|
132 | };
|
133 | this.settings = this.prepareSettings();
|
134 | window.addEventListener('error', this.onerror, false);
|
135 | window.addEventListener('unhandledrejection', this.onunhandledrejection, false);
|
136 | document.addEventListener('securitypolicyviolation', this.onsecuritypolicyviolation, false);
|
137 | }
|
138 | ShowJSError.prototype.destruct = function () {
|
139 | window.removeEventListener('error', this.onerror, false);
|
140 | window.removeEventListener('unhandledrejection', this.onunhandledrejection, false);
|
141 | document.removeEventListener('securitypolicyviolation', this.onsecuritypolicyviolation, false);
|
142 | document.removeEventListener('DOMContentLoaded', this.appendToBody, false);
|
143 | if (document.body && this.elems.container) {
|
144 | document.body.removeChild(this.elems.container);
|
145 | }
|
146 | this.state.errorBuffer = [];
|
147 | this.elems = {};
|
148 | };
|
149 | ShowJSError.prototype.setSettings = function (settings) {
|
150 | this.settings = this.prepareSettings(settings);
|
151 | if (this.state.appended) {
|
152 | this.updateUI();
|
153 | }
|
154 | };
|
155 | |
156 |
|
157 |
|
158 | ShowJSError.prototype.show = function (error) {
|
159 | if (!error) {
|
160 | this.showUI();
|
161 | return;
|
162 | }
|
163 | if (typeof error === 'string') {
|
164 | this.pushError({ message: error });
|
165 | }
|
166 | else {
|
167 | this.pushError(typeof error === 'object' ?
|
168 | error :
|
169 | new Error(error));
|
170 | }
|
171 | };
|
172 | |
173 |
|
174 |
|
175 | ShowJSError.prototype.hide = function () {
|
176 | if (this.elems.container) {
|
177 | this.elems.container.className = buildElemClass('', {
|
178 | hidden: true
|
179 | });
|
180 | }
|
181 | };
|
182 | |
183 |
|
184 |
|
185 | ShowJSError.prototype.clear = function () {
|
186 | this.state.errorBuffer = [];
|
187 | this.state.detailed = false;
|
188 | this.setCurrentError(0);
|
189 | };
|
190 | |
191 |
|
192 |
|
193 | ShowJSError.prototype.toggleView = function () {
|
194 | this.state.detailed = !this.state.detailed;
|
195 | this.updateUI();
|
196 | };
|
197 | ShowJSError.prototype.prepareSettings = function (rawSettings) {
|
198 | var settings = rawSettings || {};
|
199 | return {
|
200 | reportUrl: settings.reportUrl || '',
|
201 | templateDetailedMessage: settings.templateDetailedMessage || '',
|
202 | };
|
203 | };
|
204 | ShowJSError.prototype.pushError = function (error) {
|
205 | this.state.errorBuffer.push(error);
|
206 | this.state.errorIndex = this.state.errorBuffer.length - 1;
|
207 | this.updateUI();
|
208 | };
|
209 | ShowJSError.prototype.appendUI = function () {
|
210 | var _this = this;
|
211 | var container = document.createElement('div');
|
212 | container.className = buildElemClass('');
|
213 | this.elems.container = container;
|
214 | this.elems.close = createElem({
|
215 | name: 'close',
|
216 | props: {
|
217 | innerText: '×',
|
218 | onclick: function () {
|
219 | _this.hide();
|
220 | }
|
221 | },
|
222 | container: container
|
223 | });
|
224 | this.elems.title = createElem({
|
225 | name: 'title',
|
226 | props: {
|
227 | innerText: this.getTitle()
|
228 | },
|
229 | container: container
|
230 | });
|
231 | var body = createElem({
|
232 | name: 'body',
|
233 | container: container
|
234 | });
|
235 | this.elems.body = body;
|
236 | this.elems.message = createElem({
|
237 | name: 'message',
|
238 | props: {
|
239 | onclick: function () {
|
240 | _this.toggleView();
|
241 | }
|
242 | },
|
243 | container: body
|
244 | });
|
245 | this.elems.filename = createElem({
|
246 | name: 'filename',
|
247 | container: body
|
248 | });
|
249 | this.createActions(body);
|
250 | if (document.body) {
|
251 | document.body.appendChild(container);
|
252 | }
|
253 | else {
|
254 | document.addEventListener('DOMContentLoaded', this.appendToBody, false);
|
255 | }
|
256 | };
|
257 | ShowJSError.prototype.createActions = function (container) {
|
258 | var _this = this;
|
259 | var actions = createElem({
|
260 | name: 'actions',
|
261 | container: container
|
262 | });
|
263 | this.elems.actions = actions;
|
264 | createElem({
|
265 | tag: 'input',
|
266 | name: 'copy',
|
267 | props: {
|
268 | type: 'button',
|
269 | value: 'Copy',
|
270 | onclick: function () {
|
271 | var error = _this.getCurrentError();
|
272 | copyTextToClipboard(_this.getDetailedMessage(error));
|
273 | }
|
274 | },
|
275 | container: actions
|
276 | });
|
277 | var reportLink = createElem({
|
278 | tag: 'a',
|
279 | name: 'report-link',
|
280 | props: {
|
281 | href: '',
|
282 | target: '_blank'
|
283 | },
|
284 | container: actions
|
285 | });
|
286 | this.elems.reportLink = reportLink;
|
287 | this.elems.report = createElem({
|
288 | tag: 'input',
|
289 | name: 'report',
|
290 | props: {
|
291 | type: 'button',
|
292 | value: 'Report'
|
293 | },
|
294 | container: reportLink
|
295 | });
|
296 | this.createArrows(actions);
|
297 | };
|
298 | ShowJSError.prototype.createArrows = function (container) {
|
299 | var _this = this;
|
300 | var arrows = createElem({
|
301 | tag: 'span',
|
302 | name: 'arrows',
|
303 | container: container
|
304 | });
|
305 | this.elems.arrows = arrows;
|
306 | this.elems.prev = createElem({
|
307 | tag: 'input',
|
308 | name: 'prev',
|
309 | props: {
|
310 | type: 'button',
|
311 | value: '←',
|
312 | onclick: function () {
|
313 | _this.setCurrentError(_this.state.errorIndex - 1);
|
314 | }
|
315 | },
|
316 | container: arrows
|
317 | });
|
318 | this.elems.num = createElem({
|
319 | tag: 'span',
|
320 | name: 'num',
|
321 | props: {
|
322 | innerText: this.state.errorIndex + 1
|
323 | },
|
324 | container: arrows
|
325 | });
|
326 | this.elems.next = createElem({
|
327 | tag: 'input',
|
328 | name: 'next',
|
329 | props: {
|
330 | type: 'button',
|
331 | value: '→',
|
332 | onclick: function () {
|
333 | _this.setCurrentError(_this.state.errorIndex + 1);
|
334 | }
|
335 | },
|
336 | container: arrows
|
337 | });
|
338 | };
|
339 | ShowJSError.prototype.getDetailedMessage = function (error) {
|
340 | var text = [
|
341 | ['Title', this.getTitle(error)],
|
342 | ['Message', getMessage(error)],
|
343 | ['Filename', getFilenameWithPosition(error)],
|
344 | ['Stack', getStack(error)],
|
345 | ['Page url', window.location.href],
|
346 | ['Refferer', document.referrer],
|
347 | ['User-agent', navigator.userAgent],
|
348 | ['Screen size', getScreenSize()],
|
349 | ['Screen orientation', getScreenOrientation()],
|
350 | ['Cookie enabled', navigator.cookieEnabled]
|
351 | ].map(function (item) { return (item[0] + ': ' + item[1] + '\n'); }).join('');
|
352 | if (this.settings.templateDetailedMessage) {
|
353 | text = this.settings.templateDetailedMessage.replace(/\{message\}/, text);
|
354 | }
|
355 | return text;
|
356 | };
|
357 | ShowJSError.prototype.getTitle = function (error) {
|
358 | return error ? (error.title || 'Error') : 'No errors';
|
359 | };
|
360 | ShowJSError.prototype.showUI = function () {
|
361 | if (this.elems.container) {
|
362 | this.elems.container.className = buildElemClass('');
|
363 | }
|
364 | };
|
365 | ShowJSError.prototype.hasStack = function () {
|
366 | var error = this.getCurrentError();
|
367 | return error && (error.stack || error.filename);
|
368 | };
|
369 | ShowJSError.prototype.getCurrentError = function () {
|
370 | return this.state.errorBuffer[this.state.errorIndex];
|
371 | };
|
372 | ShowJSError.prototype.setCurrentError = function (index) {
|
373 | var length = this.state.errorBuffer.length;
|
374 | var newIndex = index;
|
375 | if (newIndex > length - 1) {
|
376 | newIndex = length - 1;
|
377 | }
|
378 | else if (newIndex < 0) {
|
379 | newIndex = 0;
|
380 | }
|
381 | this.state.errorIndex = newIndex;
|
382 | this.updateUI();
|
383 | };
|
384 | ShowJSError.prototype.updateUI = function () {
|
385 | var error = this.getCurrentError();
|
386 | if (!this.state.appended) {
|
387 | this.state.appended = true;
|
388 | this.appendUI();
|
389 | }
|
390 | if (this.elems.body) {
|
391 | this.elems.body.className = buildElemClass('body', {
|
392 | detailed: this.state.detailed,
|
393 | 'no-stack': !this.hasStack(),
|
394 | hidden: !error,
|
395 | });
|
396 | }
|
397 | if (this.elems.title) {
|
398 | this.elems.title.innerText = this.getTitle(error);
|
399 | this.elems.title.className = buildElemClass('title', {
|
400 | 'no-errors': !error
|
401 | });
|
402 | }
|
403 | if (this.elems.message) {
|
404 | this.elems.message.innerText = getMessage(error);
|
405 | }
|
406 | if (this.elems.actions) {
|
407 | this.elems.actions.className = buildElemClass('actions', { hidden: !error });
|
408 | }
|
409 | if (this.elems.reportLink) {
|
410 | this.elems.reportLink.className = buildElemClass('report', {
|
411 | hidden: !this.settings.reportUrl
|
412 | });
|
413 | }
|
414 | if (this.elems.reportLink) {
|
415 | this.elems.reportLink.href = this.settings.reportUrl
|
416 | .replace(/\{title\}/, encodeURIComponent(getMessage(error)))
|
417 | .replace(/\{body\}/, encodeURIComponent(this.getDetailedMessage(error)));
|
418 | }
|
419 | if (this.elems.filename) {
|
420 | this.elems.filename.className = buildElemClass('filename', { hidden: !error });
|
421 | this.elems.filename.innerText = getStack(error) || getFilenameWithPosition(error);
|
422 | }
|
423 | this.updateArrows(error);
|
424 | this.showUI();
|
425 | };
|
426 | ShowJSError.prototype.updateArrows = function (error) {
|
427 | var length = this.state.errorBuffer.length;
|
428 | var errorIndex = this.state.errorIndex;
|
429 | if (this.elems.arrows) {
|
430 | this.elems.arrows.className = buildElemClass('arrows', { hidden: !error });
|
431 | }
|
432 | if (this.elems.prev) {
|
433 | this.elems.prev.disabled = !errorIndex;
|
434 | }
|
435 | if (this.elems.num) {
|
436 | this.elems.num.innerText = (errorIndex + 1) + '\u2009/\u2009' + length;
|
437 | }
|
438 | if (this.elems.next) {
|
439 | this.elems.next.disabled = errorIndex === length - 1;
|
440 | }
|
441 | };
|
442 | return ShowJSError;
|
443 | }());
|
444 |
|
445 | var showJSError = new ShowJSError();
|
446 |
|
447 | return showJSError;
|
448 |
|
449 | }));
|