Andrii Shupta
Posted on August 16, 2022
๐ค Why do we need cross-origin iframe communication?
Imagine that you need to integrate with the "3rd party service" that would be used as part of your application.
Both of your companies are just start-ups, and we don't have a complete variety of tools that will make our lives easier, so we choose iframe
as the first option. We must integrate what we have now for the beta version. After that, we will refactor the code and will use edge technologies, as our manager promised(๐)
Their app(as an example) could show private information, possibly, some real-time bank details / shipping / trading details, and only available after user authorization.
๐ค What could be a better solution?
The best version of integration(IMHO) would be to get a react library with components, hooks, utils, etc., that will do everything for us. For example, check out React Stripe.js Components. Second best - take an Open API(example Stripe API) and implement our own components.
๐คจ What are we going to build?
๐ญ Idea summary
As the Parent app, we want to login within the iframe
with some token, so the iframe
could show relative information. Every N mins(5 secs in this case), our token would expire, and the iframe
needs to request another. As a bonus, we can change a theme from dark to light, which could happen from both sides.
Mostly I would list code that is only related to the iframe
and Web API
part and won't focus on things like an app creation or an explanation of "how to deploy to Vercel".
The Parent and the Child apps would be our actual implementation of what we need. For the front-end, we are going to use Next.js and Chakra-UI for components. We would deploy apps on Vercel and Netlify(to be truly cross-origin).
Also, I would use Nrwl Nx's workspaces to have monorepo, keeping run/build processes seamless.
๐จโ๐ป Code(skip to this if you don't want to read the Intro)
๐ค "The Communicator."
๐ https://iframe-communicator.vercel.app
๐ Github: https://github.com/andriishupta/iframe-communicator
It is a "special" app you could use for real-world testing to see how messaging works in your app.
๐ง Parent code
๐ link to the deployed app
๐ source code is available for copying here
As for the Parent app, we will surely have iframe
rendered on our side. Let's start from it:
- iframeRef is our React.js reference to the DOM element, so we can later use it
- onLoad - this would send my initial token
Next: how we send the message is Window.postMessage
The window.postMessage() method safely enables cross-origin communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it.
const postMessage = (message: Message) => {
iframeRef.current.contentWindow.postMessage(message, CHILD_APP_URL); // OR use '*' to handle all origins
};
postMessage
takes a message: Message
as the argument - it is our own message kind that we selected and agreed with the Child app to pass through:
The data is serialized using the structured clone algorithm. This means you can pass a broad variety of data objects safely to the destination window without having to serialize them yourself.
To send actual message we are using iframeRef.current.contentWindow
as our targetWindow
(from documentation) and the function's second parameter is targetOrigin
:
Specifies what the origin of targetWindow must be for the event to be dispatched, either as the literal string "*"
I know my targetOrigin
, so I am passing it and suggesting you not neglect security risks.
Last but not least, we want to listen to messages from the Child!
Security and filtering: we accept only our messages that we are sure in
// skip other messages for security reasons and avoid extensions alerts in console
if (event.origin !== CHILD_APP_URL) {
return;
}
Now, let's get the data from the MessageEvent and do some checks and act by business logic:
if (message?.type === 'token-expired-from-child') {
...
} else if (message?.type === 'theme-from-child') {
...
} else {
// in case of some random message
}
*for more options this code could be improved with switch/case(who likes), ternary operator, or object literals.
Finish up by adding a listener and return a callback for removal, so when a component goes down, you navigate to another page, where you don't need to listen for an iframe
.
window.addEventListener('message', handler);
return () => window.removeEventListener('message', handler);
๐ถ Child code
๐ link to the deployed app
๐ source code is available for copying here
The approach is the same for the Child app, with a twist of where to call postMessage - window.parent
.
And listening to the messages differs in type
.
๐ Links
๐จ Parent app: https://cross-origin-iframe-communication-with-nextjs-parent-app.vercel.app
๐จโ๐ป Github: https://github.com/andriishupta/cross-origin-iframe-communication-with-nextjs
๐ค "The Communicator": https://iframe-communicator.vercel.app
๐จโ๐ป Github for "The Communicator": https://github.com/andriishupta/cross-origin-iframe-communication-with-nextjs
๐ Summary
Cross-Origin iframe communication could come in quite handy in specific situations, and we totally could take advantage of two-way messaging to make that even more dynamic. Check for yourself by clicking the examples.
Thanks for reading!
Posted on August 16, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.