UNPKG

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