-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f921ef1
commit 9b2a083
Showing
12 changed files
with
346 additions
and
166 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import path from 'path'; | ||
import resolve from '@rollup/plugin-node-resolve'; | ||
import commonjs from '@rollup/plugin-commonjs'; | ||
import replace from '@rollup/plugin-replace'; | ||
import ts from 'rollup-plugin-typescript2'; | ||
|
||
const extensions = ['.js', '.ts', '.tsx', 'jsx']; | ||
|
||
export default { | ||
input: path.join(__dirname, 'src/iframeScript.tsx'), | ||
|
||
plugins: [ | ||
replace({ | ||
preventAssignment: true, | ||
values: { | ||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) | ||
} | ||
}), | ||
commonjs(), | ||
resolve({ extensions }), | ||
|
||
ts({ | ||
tsconfig: path.join('./tsconfig.build.json'), | ||
extensions | ||
}) | ||
], | ||
output: { | ||
name: 'iframe-bundle', | ||
file: path.join(__dirname, './lib/iframe-bundle.js') | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
import iframeScript from 'iframeScript'; | ||
import { parse } from 'stacktrace-parser'; | ||
|
||
import * as errorTypeHandler from './view/errorTypeHandler'; | ||
import { | ||
TYPE_UNHANDLED_ERROR, | ||
TYPE_UNHANDLED_REJECTION, | ||
TYPE_BUILD_ERROR, | ||
TYPE_BUILD_OK, | ||
TYPE_REFRESH, | ||
STACK_TRACE_LIMIT | ||
} from './constants'; | ||
|
||
let isRegistered = false; | ||
let stackTraceLimit: number | undefined = undefined; | ||
|
||
let iframe: null | HTMLIFrameElement = null; | ||
let isLoadingIframe: boolean = false; | ||
let isIframeReady: boolean = false; | ||
let errorType: errorTypeHandler.ErrorTypeEvent; | ||
|
||
declare global { | ||
interface Window { | ||
__SHUVI_ERROR_OVERLAY_GLOBAL_HOOK__: string | {}; | ||
} | ||
} | ||
|
||
const iframeStyle = { | ||
position: 'fixed', | ||
top: '0', | ||
left: '0', | ||
width: '100%', | ||
height: '100%', | ||
border: 'none', | ||
'z-index': 2147483647 | ||
}; | ||
|
||
function onUnhandledError(ev: ErrorEvent) { | ||
const error = ev?.error; | ||
if (!error || !(error instanceof Error) || typeof error.stack !== 'string') { | ||
// A non-error was thrown, we don't have anything to show. | ||
return; | ||
} | ||
|
||
errorType = { | ||
type: TYPE_UNHANDLED_ERROR, | ||
reason: error, | ||
frames: parse(error.stack) | ||
}; | ||
update(); | ||
} | ||
|
||
function onUnhandledRejection(ev: PromiseRejectionEvent) { | ||
const reason = ev?.reason; | ||
if ( | ||
!reason || | ||
!(reason instanceof Error) || | ||
typeof reason.stack !== 'string' | ||
) { | ||
// A non-error was thrown, we don't have anything to show. | ||
return; | ||
} | ||
|
||
errorType = { | ||
type: TYPE_UNHANDLED_REJECTION, | ||
reason: reason, | ||
frames: parse(reason.stack) | ||
}; | ||
update(); | ||
} | ||
|
||
function startReportingRuntimeErrors({ onError }: { onError: () => void }) { | ||
if (isRegistered) { | ||
return; | ||
} | ||
isRegistered = true; | ||
|
||
try { | ||
const limit = Error.stackTraceLimit; | ||
Error.stackTraceLimit = STACK_TRACE_LIMIT; | ||
stackTraceLimit = limit; | ||
} catch {} | ||
|
||
window.addEventListener('error', ev => { | ||
onError(); | ||
onUnhandledError(ev); | ||
}); | ||
window.addEventListener('unhandledrejection', ev => { | ||
onError(); | ||
onUnhandledRejection(ev); | ||
}); | ||
} | ||
|
||
function stopReportingRuntimeErrors() { | ||
if (!isRegistered) { | ||
return; | ||
} | ||
isRegistered = false; | ||
|
||
if (stackTraceLimit !== undefined) { | ||
try { | ||
Error.stackTraceLimit = stackTraceLimit; | ||
} catch {} | ||
stackTraceLimit = undefined; | ||
} | ||
|
||
window.removeEventListener('error', onUnhandledError); | ||
window.removeEventListener('unhandledrejection', onUnhandledRejection); | ||
} | ||
|
||
function onBuildOk() { | ||
errorType = { type: TYPE_BUILD_OK }; | ||
update(); | ||
} | ||
|
||
function onBuildError(message: string) { | ||
errorType = { type: TYPE_BUILD_ERROR, message }; | ||
update(); | ||
} | ||
|
||
function onRefresh() { | ||
errorType = { type: TYPE_REFRESH }; | ||
} | ||
|
||
function applyStyles(element: HTMLElement, styles: Object) { | ||
element.setAttribute('style', ''); | ||
for (const key in styles) { | ||
if (!Object.prototype.hasOwnProperty.call(styles, key)) { | ||
continue; | ||
} | ||
//@ts-ignore | ||
element.style[key] = styles[key]; | ||
} | ||
} | ||
|
||
function update() { | ||
// Loading iframe can be either sync or async depending on the browser. | ||
if (isLoadingIframe) { | ||
// Iframe is loading. | ||
// First render will happen soon--don't need to do anything. | ||
return; | ||
} | ||
if (isIframeReady) { | ||
// Iframe is ready. | ||
// Just update it. | ||
updateIframeContent(); | ||
return; | ||
} | ||
// We need to schedule the first render. | ||
isLoadingIframe = true; | ||
const loadingIframe = window.document.createElement('iframe'); | ||
applyStyles(loadingIframe, iframeStyle); | ||
loadingIframe.onload = function () { | ||
const iframeDocument = loadingIframe.contentDocument; | ||
if (iframeDocument != null && iframeDocument.body != null) { | ||
iframe = loadingIframe; | ||
const script = | ||
loadingIframe.contentWindow!.document.createElement('script'); | ||
script.type = 'text/javascript'; | ||
script.innerHTML = iframeScript; | ||
iframeDocument.body.appendChild(script); | ||
} | ||
}; | ||
const appDocument = window.document; | ||
appDocument.body.appendChild(loadingIframe); | ||
} | ||
|
||
function updateIframeContent() { | ||
if (!iframe) { | ||
throw new Error('Iframe has not been created yet.'); | ||
} | ||
|
||
//@ts-ignore | ||
const isRendered = iframe.contentWindow!.updateContent(errorType); | ||
|
||
if (!isRendered) { | ||
window.document.body.removeChild(iframe); | ||
iframe = null; | ||
isIframeReady = false; | ||
} | ||
} | ||
|
||
window.__SHUVI_ERROR_OVERLAY_GLOBAL_HOOK__ = | ||
window.__SHUVI_ERROR_OVERLAY_GLOBAL_HOOK__ || {}; | ||
|
||
//@ts-ignore | ||
window.__SHUVI_ERROR_OVERLAY_GLOBAL_HOOK__.iframeReady = | ||
function iframeReady() { | ||
isIframeReady = true; | ||
isLoadingIframe = false; | ||
updateIframeContent(); | ||
}; | ||
|
||
export { getErrorByType } from './view/helpers/getErrorByType'; | ||
export { getServerError } from './view/helpers/nodeStackFrames'; | ||
export { | ||
onBuildError, | ||
onBuildOk, | ||
onRefresh, | ||
startReportingRuntimeErrors, | ||
stopReportingRuntimeErrors | ||
}; |
Oops, something went wrong.