show-js-error
Version:
Show a message about a js error in any browser
481 lines (472 loc) • 20.1 kB
JavaScript
/*! show-js-error | © 2025 Denis Seleznev | MIT License | https://github.com/hcodes/show-js-error/ */
(function (exports) {
'use strict';
function getScreenSize() {
return [screen.width, screen.height, screen.colorDepth].join('×');
}
function getScreenOrientation() {
var _a;
return typeof screen.orientation === 'string' ? screen.orientation : (_a = screen.orientation) === null || _a === void 0 ? void 0 : _a.type;
}
function copyTextToClipboard(text) {
var textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
try {
textarea.select();
document.execCommand('copy');
}
catch (_a) {
alert('Copying text is not supported in this browser.');
}
document.body.removeChild(textarea);
}
function injectStyle(style) {
var styleNode = document.createElement('style');
document.body.appendChild(styleNode);
styleNode.textContent = style;
return styleNode;
}
function createElem(data) {
var elem = document.createElement(data.tag || 'div');
if (data.props) {
addProps(elem, data.props);
}
elem.className = buildElemClass(data.name);
data.container.appendChild(elem);
return elem;
}
function addProps(elem, props) {
Object.keys(props).forEach(function (key) {
elem[key] = props[key];
});
}
function buildElemClass(name, mod) {
var elemName = 'show-js-error';
if (name) {
elemName += '__' + name;
}
var className = elemName;
if (mod) {
Object.keys(mod).forEach(function (modName) {
var modValue = mod[modName];
if (modValue === false || modValue === null || modValue === undefined || modValue === '') {
return;
}
if (mod[modName] === true) {
className += ' ' + elemName + '_' + modName;
}
else {
className += ' ' + elemName + '_' + modName + '_' + modValue;
}
});
}
return className;
}
function getStack(error) {
return error && error.stack || '';
}
function getMessage(error) {
return error && error.message || '';
}
function getValue(value, defaultValue) {
return typeof value === 'undefined' ? defaultValue : value;
}
function getFilenameWithPosition(error) {
if (!error) {
return '';
}
var text = error.filename || '';
if (typeof error.lineno !== 'undefined') {
text += ':' + getValue(error.lineno, '');
if (typeof error.colno !== 'undefined') {
text += ':' + getValue(error.colno, '');
}
}
return text;
}
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}';
var ShowJSError = /** @class */ (function () {
function ShowJSError() {
var _this = this;
this.elems = {};
this.state = {
appended: false,
detailed: false,
errorIndex: 0,
errorBuffer: [],
};
this.onerror = function (event) {
var error = event.error ? event.error : event;
_this.pushError({
title: 'JavaScript Error',
message: error.message,
filename: error.filename,
colno: error.colno,
lineno: error.lineno,
stack: error.stack,
});
};
this.onsecuritypolicyviolation = function (error) {
_this.pushError({
title: 'CSP Error',
message: "blockedURI: ".concat(error.blockedURI || '', "\n violatedDirective: ").concat(error.violatedDirective, " || ''\n originalPolicy: ").concat(error.originalPolicy || ''),
colno: error.columnNumber,
filename: error.sourceFile,
lineno: error.lineNumber,
});
};
this.onunhandledrejection = function (error) {
_this.pushError({
title: 'Unhandled promise rejection',
message: error.reason.message,
colno: error.reason.colno,
filename: error.reason.filename,
lineno: error.reason.lineno,
stack: error.reason.stack,
});
};
this.appendToBody = function () {
document.removeEventListener('DOMContentLoaded', _this.appendToBody, false);
if (_this.elems.container) {
_this.styleNode = injectStyle(STYLE);
document.body.appendChild(_this.elems.container);
}
};
this.settings = this.prepareSettings();
if (typeof window === 'undefined') {
return;
}
window.addEventListener('error', this.onerror, false);
window.addEventListener('unhandledrejection', this.onunhandledrejection, false);
document.addEventListener('securitypolicyviolation', this.onsecuritypolicyviolation, false);
}
ShowJSError.prototype.destruct = function () {
var _a;
window.removeEventListener('error', this.onerror, false);
window.removeEventListener('unhandledrejection', this.onunhandledrejection, false);
document.removeEventListener('securitypolicyviolation', this.onsecuritypolicyviolation, false);
document.removeEventListener('DOMContentLoaded', this.appendToBody, false);
if (document.body && this.elems.container) {
document.body.removeChild(this.elems.container);
}
this.state.errorBuffer = [];
this.elems = {};
if (this.styleNode) {
(_a = this.styleNode.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(this.styleNode);
this.styleNode = undefined;
}
};
ShowJSError.prototype.setSettings = function (settings) {
this.settings = this.prepareSettings(settings);
if (this.state.appended) {
this.updateUI();
}
};
/**
* Show error panel with transmitted error.
*/
ShowJSError.prototype.show = function (error) {
if (!error) {
this.showUI();
return;
}
if (typeof error === 'string') {
this.pushError({ message: error });
}
else {
this.pushError(typeof error === 'object' ?
error :
new Error(error));
}
};
/**
* Hide error panel.
*/
ShowJSError.prototype.hide = function () {
if (this.elems.container) {
this.elems.container.className = buildElemClass('', {
size: this.settings.size,
hidden: true
});
}
};
/**
* Clear error panel.
*/
ShowJSError.prototype.clear = function () {
this.state.errorBuffer = [];
this.state.detailed = false;
this.setCurrentError(0);
};
/**
* Toggle view (shortly/detail).
*/
ShowJSError.prototype.toggleView = function () {
this.state.detailed = !this.state.detailed;
this.updateUI();
};
ShowJSError.prototype.prepareSettings = function (rawSettings) {
var settings = rawSettings || {};
return {
size: settings.size || 'normal',
reportUrl: settings.reportUrl || '',
templateDetailedMessage: settings.templateDetailedMessage || '',
errorFilter: settings.errorFilter || function () { return true; },
};
};
ShowJSError.prototype.pushError = function (error) {
if (!this.settings.errorFilter(error)) {
return;
}
this.state.errorBuffer.push(error);
this.state.errorIndex = this.state.errorBuffer.length - 1;
this.updateUI();
};
ShowJSError.prototype.appendUI = function () {
var _this = this;
var container = document.createElement('div');
container.className = buildElemClass('', {
size: this.settings.size,
});
this.elems.container = container;
this.elems.close = createElem({
name: 'close',
props: {
innerText: '×',
onclick: function () {
_this.hide();
}
},
container: container
});
this.elems.title = createElem({
name: 'title',
props: {
innerText: this.getTitle()
},
container: container
});
var body = createElem({
name: 'body',
container: container
});
this.elems.body = body;
this.elems.message = createElem({
name: 'message',
props: {
onclick: function () {
_this.toggleView();
}
},
container: body
});
this.elems.filename = createElem({
name: 'filename',
container: body
});
this.createActions(body);
if (document.body) {
document.body.appendChild(container);
this.styleNode = injectStyle(STYLE);
}
else {
document.addEventListener('DOMContentLoaded', this.appendToBody, false);
}
};
ShowJSError.prototype.createActions = function (container) {
var _this = this;
var actions = createElem({
name: 'actions',
container: container
});
this.elems.actions = actions;
createElem({
tag: 'input',
name: 'copy',
props: {
type: 'button',
value: 'Copy',
onclick: function () {
var error = _this.getCurrentError();
copyTextToClipboard(_this.getDetailedMessage(error));
}
},
container: actions
});
var reportLink = createElem({
tag: 'a',
name: 'report-link',
props: {
href: '',
target: '_blank'
},
container: actions
});
this.elems.reportLink = reportLink;
this.elems.report = createElem({
tag: 'input',
name: 'report',
props: {
type: 'button',
value: 'Report'
},
container: reportLink
});
this.createArrows(actions);
};
ShowJSError.prototype.createArrows = function (container) {
var _this = this;
var arrows = createElem({
tag: 'span',
name: 'arrows',
container: container
});
this.elems.arrows = arrows;
this.elems.prev = createElem({
tag: 'input',
name: 'prev',
props: {
type: 'button',
value: '←',
onclick: function () {
_this.setCurrentError(_this.state.errorIndex - 1);
}
},
container: arrows
});
this.elems.num = createElem({
tag: 'span',
name: 'num',
props: {
innerText: this.state.errorIndex + 1
},
container: arrows
});
this.elems.next = createElem({
tag: 'input',
name: 'next',
props: {
type: 'button',
value: '→',
onclick: function () {
_this.setCurrentError(_this.state.errorIndex + 1);
}
},
container: arrows
});
};
ShowJSError.prototype.getDetailedMessage = function (error) {
var text = [
['Title', this.getTitle(error)],
['Message', getMessage(error)],
['Filename', getFilenameWithPosition(error)],
['Stack', getStack(error)],
['Page url', window.location.href],
['Refferer', document.referrer],
['User-agent', navigator.userAgent],
['Screen size', getScreenSize()],
['Screen orientation', getScreenOrientation()],
['Cookie enabled', navigator.cookieEnabled]
].map(function (item) { return (item[0] + ': ' + item[1] + '\n'); }).join('');
if (this.settings.templateDetailedMessage) {
text = this.settings.templateDetailedMessage.replace(/\{message\}/, text);
}
return text;
};
ShowJSError.prototype.getTitle = function (error) {
return error ? (error.title || 'Error') : 'No errors';
};
ShowJSError.prototype.showUI = function () {
if (this.elems.container) {
this.elems.container.className = buildElemClass('', {
size: this.settings.size,
});
}
};
ShowJSError.prototype.hasStack = function () {
var error = this.getCurrentError();
return error && (error.stack || error.filename);
};
ShowJSError.prototype.getCurrentError = function () {
return this.state.errorBuffer[this.state.errorIndex];
};
ShowJSError.prototype.setCurrentError = function (index) {
var length = this.state.errorBuffer.length;
var newIndex = index;
if (newIndex > length - 1) {
newIndex = length - 1;
}
else if (newIndex < 0) {
newIndex = 0;
}
this.state.errorIndex = newIndex;
this.updateUI();
};
ShowJSError.prototype.updateUI = function () {
var error = this.getCurrentError();
if (!this.state.appended) {
this.state.appended = true;
this.appendUI();
}
if (this.elems.body) {
this.elems.body.className = buildElemClass('body', {
detailed: this.state.detailed,
'no-stack': !this.hasStack(),
hidden: !error,
});
}
if (this.elems.title) {
this.elems.title.innerText = this.getTitle(error);
this.elems.title.className = buildElemClass('title', {
'no-errors': !error
});
}
if (this.elems.message) {
this.elems.message.innerText = getMessage(error);
}
if (this.elems.actions) {
this.elems.actions.className = buildElemClass('actions', { hidden: !error });
}
if (this.elems.reportLink) {
this.elems.reportLink.className = buildElemClass('report', {
hidden: !this.settings.reportUrl
});
}
if (this.elems.reportLink) {
this.elems.reportLink.href = this.settings.reportUrl
.replace(/\{title\}/, encodeURIComponent(getMessage(error)))
.replace(/\{body\}/, encodeURIComponent(this.getDetailedMessage(error)));
}
if (this.elems.filename) {
this.elems.filename.className = buildElemClass('filename', { hidden: !error });
this.elems.filename.innerText = getStack(error) || getFilenameWithPosition(error);
}
this.updateArrows(error);
this.showUI();
};
ShowJSError.prototype.updateArrows = function (error) {
var length = this.state.errorBuffer.length;
var errorIndex = this.state.errorIndex;
if (this.elems.arrows) {
this.elems.arrows.className = buildElemClass('arrows', { hidden: !error });
}
if (this.elems.prev) {
this.elems.prev.disabled = !errorIndex;
}
if (this.elems.num) {
this.elems.num.innerText = (errorIndex + 1) + '/' + length;
}
if (this.elems.next) {
this.elems.next.disabled = errorIndex === length - 1;
}
};
return ShowJSError;
}());
var showJSError = new ShowJSError();
if (typeof window !== 'undefined') {
window.showJSError = showJSError;
}
exports.showJSError = showJSError;
return exports;
})({});