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