shadow-function
Version:
ioing lib - shadow Function, worker Function
150 lines (133 loc) • 4.6 kB
text/typescript
import { TryAgain } from '../try-again/index'
type options = {
receiver?: string,
timeout?: number,
reportUrl?: string,
dataType?: 'module' | 'json'
}
const documentElement = document.documentElement as HTMLHtmlElement
const clearUnsafe = `
function clearUnsafe (proto) {
var props = Object.getOwnPropertyNames(proto)
props.map(function (key) {
try {
proto[key] = null
delete proto[key]
} catch (e) {}
})
}
clearUnsafe(Document.prototype)
clearUnsafe(Function.prototype)
clearUnsafe(Object.prototype)
clearUnsafe(HTMLElement.prototype)
clearUnsafe(Element.prototype)
clearUnsafe(Node.prototype)
clearUnsafe(String.prototype)
clearUnsafe(window)
`
const getShadowModule = function (url: string, options: options = { receiver: '', timeout: 30000, dataType: 'module' }) {
return new Promise(function (resolve, reject) {
let { dataType, receiver, timeout } = options
let iframe: HTMLIFrameElement
let tryObj: TryAgain
let timeoutId: any
timeout = timeout || 30000
// 异常尝试
tryObj = new TryAgain(send, { timeout: 3000, polls: 2 })
// abort
function abort () {
clearTimeout(timeoutId)
window.removeEventListener('online', send, false)
iframe.parentElement && documentElement.removeChild(iframe)
}
// over
function over (type: string) {
abort()
tryObj.try()
if (tryObj.polls === 0) {
reject(type)
}
}
function hashSource () {
return (new Array(8 + Math.round(Math.random() * 8))).join(',').split(',').map(() => {
return String.fromCharCode(97 + Math.round(Math.random() * 25))
}).join('')
}
function send () {
let hash = hashSource()
let script = document.createElement('script')
script.src = url
url = script.src
iframe = document.createElement('iframe')
iframe.style.display = 'none'
iframe.csp = `script-src 'unsafe-inline' ${url.split(/\?|\#/)[0]} 'nonce-${hash}'; ${options.reportUrl ? 'report-uri ' + options.reportUrl : ''}`
iframe.src = URL.createObjectURL(new Blob([`
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="${iframe.csp}">
<script id=${hash} nonce=${hash} src="${url}" async></script>
<script nonce=${hash}>
(function () {
var parent = window.parent
var script = document.getElementById('${hash}')
window['${receiver}'] = window['moduleExportsReceiver'] = function (data) {
window['moduleExportsReceiver'] = function () {}
parent['on${hash}'] ? parent['on${hash}']({ data: { response: data, src: '${url}', hash: '${hash}' } }) : parent.postMessage({ response: JSON.parse(JSON.stringify(data)), src: '${url}', hash: '${hash}' }, '${location.origin}')
}
script.onerror = window['getModuleError'] = function () {
window['moduleExportsReceiver'](null)
}
script.onload = window['getModuleLoaded'] = function () {
var data = Object.keys(window.exports).length ? window.exports : window.module.exports
window['moduleExportsReceiver'](data)
}
${dataType === 'json' ? clearUnsafe : ''}
})()
window.module = {
exports: {}
}
window.exports = {}
</script>
<head>
<body>
</body>
</html>
`], { type: 'text/html' }))
if (dataType !== 'module') {
iframe.setAttribute('sandbox', 'allow-scripts')
}
window['on' + hash] = function (data: any): void {
let result = data.data
let response = result ? result.response : ''
// filter message
if (result.src !== url || result.hash !== hash) return
// remove iframe
abort()
delete window['on' + hash]
// resolve | reject
if (response) {
resolve(response)
} else {
reject(result)
}
}
window.addEventListener('message', window['on' + hash], false)
documentElement.appendChild(iframe)
}
// 超时处理
timeoutId = setTimeout(() => {
over('timeout')
}, timeout)
send()
// 断网重连
if (navigator.onLine === false) {
abort()
tryObj.stop()
window.addEventListener('online', send, false)
}
})
}
export {
getShadowModule
}