0

I tried multiple ways, i'm trying to integrate my banner from tryhackme or let;s say from any platform we get a script src we want to integrate that but react is not rendering it.

<script src="https://tryhackme.com/badge/170258"></script>

Here's my code block for reactjs

import React, { Component } from "react";

// const Awards = (props) => {
//     return (
//         <Container>
//             <div>
            
//             {/* <img src="https://tryhackme-badges.s3.amazonaws.com/rootsid.png" alt="TryHackMe"></img> */}
//             </div>
//         </Container>
//     )
// }

// const Container = styled.main`
//     color: white;
// `;

export default class Awards extends Component {
    componentDidMount() {
      const script = document.createElement("script");
  
      script.src = "https://tryhackme.com/badge/170258";
      script.async = true;
      
      var scripttag = document.getElementById('scriptTarget')
      scripttag.appendChild(script);
    }
  
    render() {
      return (
        <div>
          <div id="scriptTarget" />
          <div className="main">
            // Other stuff
          </div>
        </div>
      );
    }
  }

Rather than image src, I'm trying to include script tag in react js so it can be dynamic. I tried helmet, react-safe but can't get it to work. Here's the fiddle link as well.

https://jsfiddle.net/h2x5pkfy/

fast-reflexes
  • 4,891
  • 4
  • 31
  • 44
Sid Kaushik
  • 567
  • 4
  • 14
  • 1
    Does this answer your question? [Adding script tag to React/JSX](https://stackoverflow.com/questions/34424845/adding-script-tag-to-react-jsx) – fast-reflexes Jul 17 '21 at 06:34
  • I tried this as well but can't get it to work as well. – Sid Kaushik Jul 17 '21 at 07:07
  • The JsFiddle should work but you don't render your React anywhere. You have to [manually render your React app into a real DOM](https://reactjs.org/docs/rendering-elements.html#rendering-an-element-into-the-dom) – fast-reflexes Jul 17 '21 at 07:19
  • fiddle is just for your reference, i'm rendering this component in my main app. It renders out the text like `other stuff` but don't render script tag source which should have been a banner. – Sid Kaushik Jul 17 '21 at 07:21
  • I think this problem is not related to React ... The script is imported as it should for me with your code.. try adding `document.open()` and then `document.close()` in your imported script (before and after the call to `document.write()`. – fast-reflexes Jul 17 '21 at 07:38
  • that script is remote and coming from a link, how can I edit that ? – Sid Kaushik Jul 17 '21 at 07:53
  • I thought you had control over it. For me it says after fetching the script `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`](https://developer.mozilla.org/en-US/docs/Web/API/Document/write) states that if a document is already loaded, calling `document.write` implicitly calls `document.open` but this is apparently not enough for external scripts (have to call it implicitly). Besides, if this call would succeed, the whole React app is gone anyways.. – fast-reflexes Jul 17 '21 at 08:00
  • ...since this overwrites the entire page. If you HAVE to use the script like this and you can't change it, I would put it in a frame on the page instead. – fast-reflexes Jul 17 '21 at 08:00
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/234997/discussion-between-sid-kaushik-and-fast-reflexes). – Sid Kaushik Jul 17 '21 at 08:06

1 Answers1

2

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

Result

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>
fast-reflexes
  • 4,891
  • 4
  • 31
  • 44
  • but from where frame.html comes in ? – Sid Kaushik Jul 17 '21 at 10:26
  • Store the upper content as `frame.html` as a static file in your project. – fast-reflexes Jul 17 '21 at 10:50
  • for some reason it make a copy of whole website in that iframe if i do this https://pasteboard.co/KbzQJb8.png – Sid Kaushik Jul 17 '21 at 10:53
  • I don't understand... is your actual use case different from what you have described? Yes, it will load an entire page in the frame but if the page is just the upper content that I showed and the script load only that badge, then that's all that should be showed ... If the use case is different, then you will have a different result. – fast-reflexes Jul 17 '21 at 11:02
  • My use case is exactly what you did just want that script-src as a picture in a div. I'm surprised i'm getting different results or sandbox is behaving differently. – Sid Kaushik Jul 17 '21 at 11:04
  • Run it locally, as I said, sandboxes has restrictions. – fast-reflexes Jul 17 '21 at 11:29
  • Simply store the two files locally (with the content from above) as `frame.html` and `index.html` and open `index.html` with Chrome and you'll see that it works. The rest is just a matter of adapting it to your project. – fast-reflexes Jul 17 '21 at 11:40
  • I saw you can use [`srcdoc`](https://www.w3schools.com/tags/att_iframe_srcdoc.asp) instead of `src` to supply the `iframe` content inline instead of in a separate file. Here's finally a working [sandbox](https://codesandbox.io/s/iframe-0r9gk) for you – fast-reflexes Jul 17 '21 at 13:38