'How do I post a message to a iframe (not webview) in React-Native?
I'm currently working with React-Native and need some ideas on how to solve an issue I'm having involving iframes (not WebView).
I have a functional component written in TypeScript that uses React.createElement to create the iframe and trying to call/invoke a function after the iframe has been created/mounted without re-rendering it. My original plan was to try and inject JavaScript similar to how a WebView does it, however I've been unable to do this without re-rending. So, my plan B is to message the iframe using postMessage where during onMessage a function will run window[stringFunction](parameters) (using window to run a function by using its name in string form (similar to eval)).
This should work, however I need access to the iframe's contentWindow to use postMessage, unless there is a different method to communicate with the iframe without re-rendering it. React-Native uses 'ref' instead of 'window.getElementById' to reference components after mounting/creation, however ref doesn't provide access to the iframe's contentWindow or postMessage like window.getElementById does...
My question is how I get access to postMessage to send a message to the iframe and do it without re-rending it?
Component:
// Component (Removed majority of unrelated code)
import * as React from 'react';
interface SourceHTML { // Source object passed by source prop
html: string;
}
type IFrameProps = { // Props
source: SourceHTML,
}
export interface Ref { // Methods - Accessible by parent when using ref.current
injectJavaScript?: (value: string) => void,
}
const IFrameComponent = React.forwardRef<Ref, IFrameProps>((props, ref) => {
const iframeRef = React.useRef(null); // IFrame reference
const { source } = props;
React.useImperativeHandle(ref, () => ({
injectJavaScript: (message: string) => {
if (iframeRef && iframeRef.current) {
if(message.toString().includes("true")) {
const pieces: Array<string> = message.toString().split(";")[0].trim().split("(")
const stringFunction = pieces[0].trim()
const parameters = pieces[1].replace(")", "")
const messageObj = {
action: "evaluateFunction",
stringFunction: stringFunction,
parameters: parameters,
}
iframeRef.current.contentWindow.postMessage(messageObj, '*'); // <---- This does not work...
}
}
}}))
const handleInjectedJavaScript = (html) => {
if (html) {
const javascript = `
function evaluateFunction(stringFunction, parameters) {
window[stringFunction](parameters)
}
window.addEventListener("message", (event) => {
alert("Message recieved")
if(typeof message === 'object' && message.action === "evaluateFunction") {
alert("Message is an object")
const {stringFunction: stringFunction, parameters: parameters} = message;
evaluateFunction(stringFunction, parameters);
}
})
`
if(!html.includes(javascript)) {
html = html.replace('</body>', `<script>${javascript}</script></body>`);
alert(!html.includes(javascript))
}
}
}
return (
React.createElement(
'iframe',
{
ref: iframeRef,
id: "IFrame",
srcDoc: handleInjectedJavaScript(source.html),
frameBorder: '0',
seamless: true,
}
)
)
})
IFrameComponent.displayName = 'IFrame';
export {IFrameComponent}
export default IFrameComponent;
Then to use:
// Simple Alert (Example)
const webViewRef = React.useRef();
const html = `
<html>
<body>
<script>
function AlertMessage(message) {
alert(message);
}
</script>
</body>
</html>
`;
webViewRef.current.injectJavaScript("alert('Hello');true;");
<IFrameComponent
ref={webViewRef}
source={{ html: html }}
/>
This example does not reflect the end result and is a very stripped down version to help isolate the issue. I've removed all the other features. Also, this example will ONLY work on the web.
I plan on releasing the full version to Github (it will be my first repository) once it has been tested and is functioning like intended. This project is based on React-Native Web WebView, however it has been completely re-written as a functional component with TypeScript AND works on android, ios and web AND has postMessage (from iframe to React-Native), onLoadStart, onLoad and onLoadEnd all working properly.
Once I've got postMessage working the other way (React-Native to iframe), then it should be compatible with the majority of npm/yarn packages that work on the native WebView. For example, this would allow you to use react-native-signature-canvas and have it fully-functional on the web.
Thank you for your time and I appreciate any help/ideas you are able to provide.
- Logan King (aka DarkComet)
Solution 1:[1]
I decided to search one last time and decided to check out the TypeScript library files and I found the reference to HTMLElement and HTMLIFrameElement... There it was, it's interface:
interface HTMLIFrameElement extends HTMLElement {
...
/**
* Retrieves the document object of the page or frame.
*/
readonly contentDocument: Document | null;
/**
* Retrieves the object of the specified.
*/
readonly contentWindow: WindowProxy | null;
...
}
It verifies that contentWindow does exist and I found a simple mistake early in the code that was the issue.
I'll clean up the edges and finish up the Github repo page I am working on for this!
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | DarkComet |