/* @flow */ import { register as registerError, unregister as unregisterError, } from './effects/unhandledError'; import { register as registerPromise, unregister as unregisterPromise, } from './effects/unhandledRejection'; import { register as registerShortcuts, unregister as unregisterShortcuts, handler as keyEventHandler, SHORTCUT_ESCAPE, SHORTCUT_LEFT, SHORTCUT_RIGHT, } from './effects/shortcuts'; import { register as registerStackTraceLimit, unregister as unregisterStackTraceLimit, } from './effects/stackTraceLimit'; import { permanentRegister as permanentRegisterConsole, registerReactStack, unregisterReactStack, } from './effects/proxyConsole'; import { massage as massageWarning } from './utils/warnings'; import { consume as consumeError, getErrorRecord, drain as drainErrors, } from './utils/errorRegister'; import type { ErrorRecordReference } from './utils/errorRegister'; import type { StackFrame } from './utils/stack-frame'; import { iframeStyle } from './styles'; import { injectCss, applyStyles } from './utils/dom/css'; import { createOverlay } from './components/overlay'; import { updateAdditional } from './components/additional'; const CONTEXT_SIZE: number = 3; let iframeReference: HTMLIFrameElement | null = null; let additionalReference = null; let errorReferences: ErrorRecordReference[] = []; let currReferenceIndex: number = -1; const css = [ '.cra-container {', ' padding-right: 15px;', ' padding-left: 15px;', ' margin-right: auto;', ' margin-left: auto;', '}', '', '@media (min-width: 768px) {', ' .cra-container {', ' width: calc(750px - 6em);', ' }', '}', '', '@media (min-width: 992px) {', ' .cra-container {', ' width: calc(970px - 6em);', ' }', '}', '', '@media (min-width: 1200px) {', ' .cra-container {', ' width: calc(1170px - 6em);', ' }', '}', ].join('\n'); function render(name: ?string, message: string, resolvedFrames: StackFrame[]) { disposeCurrentView(); const iframe = window.document.createElement('iframe'); applyStyles(iframe, iframeStyle); iframeReference = iframe; iframe.onload = () => { if (iframeReference == null) { return; } const w = iframeReference.contentWindow; const document = iframeReference.contentDocument; const { overlay, additional } = createOverlay( document, name, message, resolvedFrames, CONTEXT_SIZE, currReferenceIndex + 1, errorReferences.length, offset => { switchError(offset); }, () => { unmount(); } ); if (w != null) { w.onkeydown = event => { keyEventHandler(type => shortcutHandler(type), event); }; } injectCss(iframeReference.contentDocument, css); if (document.body != null) { document.body.appendChild(overlay); } additionalReference = additional; }; window.document.body.appendChild(iframe); } function renderErrorByIndex(index: number) { currReferenceIndex = index; const { error, unhandledRejection, enhancedFrames } = getErrorRecord( errorReferences[index] ); if (unhandledRejection) { render( 'Unhandled Rejection (' + error.name + ')', error.message, enhancedFrames ); } else { render(error.name, error.message, enhancedFrames); } } function switchError(offset) { const nextView = currReferenceIndex + offset; if (nextView < 0 || nextView >= errorReferences.length) { return; } renderErrorByIndex(nextView); } function disposeCurrentView() { if (iframeReference === null) { return; } window.document.body.removeChild(iframeReference); iframeReference = null; additionalReference = null; } function unmount() { disposeCurrentView(); drainErrors(); errorReferences = []; currReferenceIndex = -1; } function crash(error: Error, unhandledRejection = false) { if (module.hot && typeof module.hot.decline === 'function') { module.hot.decline(); } consumeError(error, unhandledRejection, CONTEXT_SIZE) .then(ref => { if (ref == null) { return; } errorReferences.push(ref); if (iframeReference !== null && additionalReference !== null) { updateAdditional( iframeReference.contentDocument, additionalReference, currReferenceIndex + 1, errorReferences.length, offset => { switchError(offset); } ); } else { if (errorReferences.length !== 1) { throw new Error('Something is *really* wrong.'); } renderErrorByIndex((currReferenceIndex = 0)); } }) .catch(e => { console.log('Could not consume error:', e); }); } function shortcutHandler(type: string) { switch (type) { case SHORTCUT_ESCAPE: { unmount(); break; } case SHORTCUT_LEFT: { switchError(-1); break; } case SHORTCUT_RIGHT: { switchError(1); break; } default: { //TODO: this break; } } } function inject() { registerError(window, error => crash(error)); registerPromise(window, error => crash(error, true)); registerShortcuts(window, shortcutHandler); registerStackTraceLimit(); registerReactStack(); permanentRegisterConsole('error', (warning, stack) => { const data = massageWarning(warning, stack); crash( // $FlowFixMe { message: data.message, stack: data.stack, __unmap_source: '/static/js/bundle.js', }, false ); }); } function uninject() { unregisterStackTraceLimit(); unregisterShortcuts(window); unregisterPromise(window); unregisterError(window); unregisterReactStack(); } export { inject, uninject };