Finally managed to get it to work!
SO first of all, the script contains only one expression: document.write(window.atob(<BASE64 encoded content forming a HTML subtree>))
.
Getting React to load the script
The solution in the proposed link works, only that allowing external scripts to write to your document is not straightforward.
Problems and thoughts
The error I receive when doing this is 170258:1 Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened.
. The docs on write
says that an already loaded document needs to be opened and closed when writing, like
document.open()
document.write(...)
document.close()
... it also says that this is done implicitly when using document.write
on a loaded document, however, the error message says it has to be an explicit call when the script is external. Nonetheless, should the script load and execute, it would overwrite the entire page and thus remove the React app... it seems a tad bit odd to use React to load a script that removes React.
Since the OP can't modify the original script and insert the necessary calls (unsure whether this would suffice), that is not an option. One idea could be to try to import the script as text and then extract the Base64 encoded string and use that internally (this would work). Nonetheless, since it would remove the React app, I don't really see the point.
Solution: iFrame
In order not to perform som odd hacks and keep the React app in place, an iFrame can be used. The iFrame can reference a document like:
<!DOCTYPE html>
<html>
<body>
<script src="https://tryhackme.com/badge/170258"></script>
</body>
</html>
Simply adding it to React works as expected
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
ReactDOM.render(
<div>
<h1>React output before the iFrame</h1>
<iframe src={"frame.html"} style={{"borderWidth": 0}}></iframe>
<h1>React output after the IFrame</h1>
</div>,
document.getElementById('root')
);
</script>
</body>
</html>
... which produces the output

However, when googling a bit on iFrames in React, it's easy to run into use cases where different kinds of problems blocking the request come up in which case one might have to take some extra measures for the iFrame to behave properly.
All in all, I think this use case is a bit unusual and personally, I would try to solve it in an altogether different way, but given that the context is what it is, this is definitely one way of doing it.
Update
To avoid using a separate file for the iframe
, the srcDoc
attribute can be used on the iframe
allowing you to specify the content inline instead, at which point we arrive at the below React component instead:
export default function App() {
const iFrame = '<div><script src="https://tryhackme.com/badge/170258"></script></div>';
return (
<div>
<h1>React output before the iFrame</h1>
<iframe title={"badge"} srcDoc={iFrame} style={{ borderWidth: 0 }}>
</iframe>
<h1>React output after the IFrame</h1>
</div>
);
}
This solution can be seen in this sandbox. Add further styling to the iframe
element to make it blend in as you want.
Finally, the corresponding one-file standalone solution (notice the escaped script
end tag which is necessary for the content string to pass):
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const iFrame = `<div><script src="https://tryhackme.com/badge/170258"><\/script></div>`
ReactDOM.render(
<div>
<h1>React output before the iFrame</h1>
<iframe title={"badge"} srcDoc={iFrame} style={{"borderWidth": 0}}></iframe>
<h1>React output after the IFrame</h1>
</div>,
document.getElementById('root')
);
</script>
</body>
</html>