react native webview postmessage doesn't trigger onmessage - javascript

React Native Version : 0.60
Library used : react-native-webview (version : 9.0.1)
Issue :
When I send postMessage (this.webView.postMessage('somedata')) from my react-native event handler to webview, the onMessage is not triggered. But onMessage does get triggered on events sent by the webview.
Solutions tried :
Added both document.eventListener and window.eventListener. Still the onMessage is NOT trigged when I postMessage from react-native.
Any help would be greatly appreciated.
Code :
// On Click, the following function will run
_sendPostMessage = () => {
//data sent to webview
this.webView.postMessage("getAuthToken");
}
// Inside render() function
<WebView
ref={(webView) => this.webView = webView}
style={styles.webView}
source={{ uri: event.ticket_form_url, headers: headers }}
injectedJavaScript={jsCode}
onMessage={this.onMessage}
renderLoading={this._renderActivityIndicator}
startInLoadingState={true}
/>
// Injected javscript code below
var jsCode = `
(function() {
window.postMessage = function(data) {
window.ReactNativeWebView.postMessage(data);
};
})()
(function() {
var originalPostMessage = window.postMessage;
var patchedPostMessage = function(message, targetOrigin, transfer) {
originalPostMessage(message, targetOrigin, transfer);
};
patchedPostMessage.toString = function() {
return String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage');
};
window.postMessage = patchedPostMessage;
})();
window.addEventListener("message", function(event) {
var uat = ''
if (window.userAuthenticationToken) {
uat = window.userAuthenticationToken()
}
window.postMessage('setAuthToken,' + uat)
});
document.addEventListener("message", function(event) {
var uat = ''
if (window.userAuthenticationToken) {
uat = window.userAuthenticationToken()
}
window.postMessage('setAuthToken,' + uat)
});
`;
// onMessage
onMessage = (event) => {
//data received from webview
console.log('Reached onMessage', event.nativeEvent.data);
var response = event.nativeEvent.data
var data = response.split(',')
}

Hello you must use post message as injectedJavascript. For example,
<WebView
source={{ uri:'https://stackoverflow.com' }}
injectedJavaScript="window.ReactNativeWebView.postMessage(document.title)"
onMessage={event => setTitle(event.nativeEvent.data)}
/>
You can use this package for webview for beauty UI.
https://github.com/ilkerkesici/react-native-beauty-webview

Related

ReactJS: Data not binding from continuous response

While integrating the aws ivs streaming channel with quiz related metadata, at that time getting the console.log of the metadata records and while passing those records into another component it is not handling any how.
A playground that i have created into codesandobx
PlayerComponent
function PlayerComponent(options) {
useEffect(() => {
const script = document.createElement("script");
script.src = "https://player.live-video.net/1.0.0/amazon-ivs-player.min.js";
script.async = true;
document.body.appendChild(script);
script.onload = (IVSPlayer) => {
if (IVSPlayer.isPlayerSupported) {
const player = IVSPlayer.create();
player.attachHTMLVideoElement(document.getElementById("video-player"));
player.load(
"https://fcc3ddae59ed.us-west-2.playback.live-video.net/api/video/v1/us-west-2.893648527354.channel.xhP3ExfcX8ON.m3u8"
);
player.play();
player.addEventListener(
IVSPlayer.PlayerEventType.TEXT_METADATA_CUE,
(cue) => {
const metadataText = cue.text;
setMetaData(metadataText);
console.log("PlayerEvent - METADATA: ", metadataText);
}
);
}
};
return () => {
document.body.removeChild(script);
};
}, []);
return (
<div ref={divEl}>
<video id="video-player" ref={videoEl} autoPlay controls></video>
{metaData !== undefined ? <QuizComponent metadata={metaData} /> : ""}
</div>
);
}
On QuizComponent would like to render those metadata
export default function QuizComponent(props) {
const questionData = props;
return (
<React.Fragment>
<h2>{questionData.metadata.question}</h2>
</React.Fragment>
);
}
But any how not able to render the data into component.
Ref example of what I am going to implement.
https://codepen.io/amazon-ivs/pen/XWmjEKN?editors=0011
I found the problem. Basically you are referring IVSPlayer as if it was the argument of the arrow function you passed to script onload, while the argument instead is an event (the onload event).
Solution: const {IVSPlayer} = window;. Infact docs say
Once amazon-ivs-player.min.js is loaded, it adds an IVSPlayer variable to the global context.
Docs also explain how to setup with NPM which you may be interested in.
I updated my playground here.
I also suggest you to edit the version of the player as the last one is 1.2.0.

Receiving message from injected JavaScript in a React Native WebView

In my React Native App, I'm using a WebView to display a google ad (AdSense) by using the "injectedJavascript" prop. The problem is I can't know the height of the ad in advance. So I give it a random height at first and when its style is updated, I plan to set its height correctly.
I assume I have to get the height in the injected JS code, and then use the "window.postMessage()" method to send it to the WebView through the "onMessage" prop.
MutationObserver combined with promises seem very appropriate for this case. For now, I'd like to just receive the message from the webview. So this is my code right now but no message is sent :
export default class App extends Component {
constructor(props) {
super(props);
}
_onMessage(e) {
console.warn(e.nativeEvent.data);
}
render() {
const jsCode = `
window._googCsa('ads', pageOptions, adblock1);
function waitForAdSense(id) {
var config = {
attributes: true,
attributeFilter: ['style'],
};
return new Promise((resolve, reject) => {
var adSenseElement = document.body.getElementById(id);
if (adSenseElement.style) {
resolve(adSenseElement.style.height);
return;
}
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.attributeName === 'style') {
observer.disconnect();
resolve(adSenseElement);
return;
}
});
});
observer.observe(adSenseElement, config);
});
}
waitForAdSense('afscontainer1').then(height => {
window.postMessage(height, '*');
});
`;
return (
<ScrollView>
<WebView
key={'AdSense'}
ref={'webview2'}
style={{ height: 300 }}
source={{
uri: isAndroid
? 'file:///android_asset/widget/adSense.html'
: './widget/index.html',
}}
javaScriptEnabled={true}
mixedContentMode="compatibility"
injectedJavaScript={jsCode}
scrollEnabled={false}
domStorageEnabled={true}
onMessage={this._onMessage}
scalesPageToFit
startInLoadingState={true}
automaticallyAdjustContentInsets={false}
/>
;
</ScrollView>
);
}
}
Though, I can make it work with this code but setTimeout is not the best solution :
window._googCsa('ads', pageOptions, adblock1);
var adSenseContainer = document.getElementById("afscontainer1");
setTimeout(function(){ window.postMessage(adSenseContainer.style.height, '*'); },1000);
Do you have any ideas ? I think my function waitForAdSense() might be bugged somehow. Thanks in advance !
The best solution would be to use AdMob instead of AdSense on a React Native mobile application. It is somewhat pointless to mess around with these issues as the AdSense javascript wasn't made with this use case in mind. Here is a library made to easily integrate AdMob within your app.
I managed to make it work with this changes in the injected code :
window._googCsa('ads', pageOptions, adblock1);
function waitForAdSense() {
var config = {
attributes: true,
attributeFilter: ["style"]
};
var adSenseContainer = document.getElementById("afscontainer1");
return new Promise((resolve, reject) => {
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.attributeName === 'style' && adSenseContainer.style.height) {
var height = adSenseContainer.style.height;
resolve(height);
observer.disconnect();
return;
};
});
});
observer.observe(adSenseContainer, config);
});
};
waitForAdSense().then(height => {
window.postMessage(height, '*');
});
Thanks for the advices !

Issue with Firebase Cloud Messaging Service Worker and self.addEventListener

I've successfully built an FCM notification service worker for my web app, and it's working OK so far. I used toastr to present notifications within the web app. I'm currently having an issue with the service worker when the web site is not open. Here is my code from firebae-messaging-sw.js:
//Firebase initialized above here
messaging.setBackgroundMessageHandler(function (payload) {
const notiTitle = payload.data.title;
var body = payload.data.body;
const opts = {
icon : "/ui/img/icons/android-chrome-256x256.png",
actions : [
{
action: 'view-ticket',
title: 'View Ticket',
icon: null
}
],
body: body
//url: link
};
self.addEventListener('notificationclick', function (event) {
const clickedNotification = event.notification;
clickedNotification.close();
if(!event.action) {
return;
}
switch(event.action) {
case 'view-ticket':
var promiseChain = clients.openWindow(payload.data.link);
break;
}
event.waitUntil(promiseChain);
});
return self.registration.showNotification(notiTitle, opts);
});
It's almost working perfectly except for one issue. When I send my first test notification, payload.data.link is parsed ok. But on the next notification, payload.data.link is not updated, so the wrong link is sent. I think that maybe self.addEventListener is in the wrong place, but I'm not sure how else to put it (I obviously can't do it after the return).
Any idea where I should put the event listener code?
I fixed it! I was able to repair this by adding a variable and moving addEventListener outside of setBackgroundMessageHandler like so:
//Firebase started up above
var clickDestination; //init this variable
//add event listener before background message handler and use clickDestination
self.addEventListener('notificationclick', function (event) {
const clickedNotification = event.notification;
clickedNotification.close();
if (!event.action) {
return;
}
if(event.action === 'view-ticket') {
var promise = new Promise(function () {
return clients.openWindow(clickDestination);
});
event.waitUntil(promise);
}
});
messaging.setBackgroundMessageHandler(function (payload) {
const notiTitle = payload.data.title;
var body = payload.data.body;
clickDestination = payload.data.link; //set clickDestination based on payload
/*self.addEventListener('notificationclick', function (event) {
event.notification.close();
event.waitUntil(self.clients.openWindow(payload.data.link));
});*/
const opts = {
icon : "/ui/img/icons/android-chrome-256x256.png",
actions : [
{
action: 'view-ticket',
title: 'View Ticket',
icon: '/ui/img/icons/ticket-icon.png'
}
],
body: body
};
return self.registration.showNotification(notiTitle, opts);

React Native - Webview call React Native function

Is that possible create a function inside the WebView component, trigger React Native function?
It's possible but I'm not sure if it's the only way to do this.
Basically you can set an onNavigationStateChange event handler, and embed function call information in navigation url, here's an example of the concept.
In React Native context
render() {
return <WebView onNavigationStateChange={this._onURLChanged.bind(this)} />
}
_onURLChanged(e) {
// allow normal the natvigation
if(!e.url.startsWith('native://'))
return true
var payload = JSON.parse(e.url.replace('native://', ''))
switch(e.functionName) {
case 'toast' :
native_toast(e.data)
break
case 'camera' :
native_take_picture(e.data)
break
}
// return false to prevent webview navitate to the location of e.url
return false
}
To invoke native method, use this just trigger webview's navigation event and embed the function call information in URL.
window.location = 'native://' + JSON.stringify({
functionName : 'toast', data : 'show toast text'
})
use onMessage eventListner on <WebView/>
<WebView onMessage={onMessage} ... />
/** on message from webView -- window.ReactNativeWebView?.postMessage(data) */
const onMessage = event => {
const {
nativeEvent: {data},
} = event;
if (data === 'goBack') {
navigation.goBack();
} else if (data?.startsWith('navigate')) {
// navigate:::routeName:::stringifiedParams
try {
const [, routeName, params] = data.split(':::');
params = params ? JSON.parse(params) : {};
navigation.navigate(routeName, params);
} catch (err) {
console.log(err);
}
}
};
use this in your HTML to post message event
window.ReactNativeWebView?.postMessage("data")
You could inject a javascript function to the webview on load and then use onMessage to get response from the function you injected more info IN Here
yes it's possible , it existe a package for that react-native-webview-bridge.
I used it heavily in production and it works perfectly.
I am not sure, but my opinion is -
You can not. Webview can load only js part which we can define in Webview component. This is totally separate than other components, it is only just a viewable area.

Reactjs: Loading view based on response

Looking for a way for React to process some json, and load views based on the response.
For example:
1- React has a form, response goes out to external API
2- API processes the input, returns a success code unless there was validation issues, and send a response back to the React app
3- React gets the json response, loads a "Success" view, or reloads the form and outputs the erros
Is there a simple way for React to handle this? Thanks in advance!
Very simple...
Basically, you need to track when you initiate request (sending data) and when request is completed (receiving response).
Based on data returned, you decide what to render...
Take a look at this example (working fiddle)
// In this example, we are using JSONPlaceholer service do real
// request and receive response;
const root = 'http://jsonplaceholder.typicode.com';
const Success = () => (<div>Success!</div>);
const Error = () => (<div>Error! Fix your data!</div>);
const Form = React.createClass({
getInitialState() {
return {
processing: false,
result: undefined,
};
},
submit(event) {
this.setState({ processing: true });
event.preventDefault();
fetch(`${root}/posts`, {
method: 'POST',
data: {
// your data here...
}
})
.then(response => {
// We simulate succesful/failed response here.
// In real world you would do something like this..
// const result = response.ok ? 'success' : 'error';
const processing = false;
const result = Math.random() > 0.5 ? 'success' : 'error';
this.setState({ result, processing });
});
},
render() {
const { result, processing } = this.state;
if (result === 'success')
return <Success />;
return (
<form>
Form content here<br/>
<button onClick={this.submit}>
{ processing ? 'Sending data...' : 'Submit' }
</button>
{ result === 'error' && <Error /> }
</form>
);
},
});
render(<Form />, document.getElementById('root'));
The easy way would be to trigger the new state with setState() from the API callback function such as in the example below, although I recommend using a library such as Redux for state management.
var MainComp = React.createClass({
getInitialState: function() {
return {someProp: ""}
},
callAPI: function() {
var root = 'http://jsonplaceholder.typicode.com';
$.ajax({
url: root + '/posts/1',
method: 'GET'
}).then(function(data) {
this.setState({someProp: data.body})
}.bind(this));
},
render: function(){
return (
<div>
<h2>{this.state.someProp}</h2>
<button onClick={this.callAPI}>Async</button>
</div>
)
}
});
React.render(<MainComp/>, document.getElementById("app"));
Please note this is a naive example, you should still cover up error cases and build a logic to trigger different views based on state.

Categories

Resources