How can I prevent going back in react-native-webview? - react-native

My app uses react-native-webview and set allowsBackForwardNavigationGestures to true.
I want to prevent going back on a specific page.
For example, after navigating to the home page from the login page, I want to make sure that I cannot go back.
I wonder if blocking back is possible in react-native-webview.
Please help.

I can't be sure but your question sure sounds like a React Navigation issue, particularly concerning Auth. Flow. Well, should that be the case, there's a pretty well written documentation on Auth Flow with good example(s) here
Hope that helps in solving your issue.

I think you could use onShouldStartLoadWithRequest and simply return false when request contains URL you do not want to allow.
From documentation:
<WebView
source={{ uri: 'https://reactnative.dev' }}
onShouldStartLoadWithRequest={(request) => {
// Only allow navigating within this website
return request.url.startsWith('https://reactnative.dev');
}}
/>

You can restrict back button from onNavigationStateChange
onNavigationStateChange={(navState) => {
if (navState.canGoBack) {
navigation.setParams({
headerLeftInfo: {
title: '',
onPress: () => //TODO:back button logic,
},
});
} else {
navigation.setParams({
headerLeftInfo: null,
});
}
}}
Reference: https://www.reactnativeschool.com/integrating-react-navigation-back-button-with-a-webview

Related

Logout user by sending them to a Logout Webview

I'm having a difficult time trying to figure out how to log out both the user on the expo app as well as logging them out from the react-native-webview.
The user can log out of the expo app by pressing logout (which just handles the auth logic), however; the user is still logged in inside the webview (which is the shop uri).
At the moment, I came up with two possible ways: either set incognito to true on the webview, or send the user to a Screen which returns another webview to the shopify's logout uri.
// sending user to shopify logout webview
//navigator
<Stack.Navigator>
<Stack.Screen
name="AccountDetails"
component={AccountDetailsScreen}
/>
<Stack.Screen
name="ShopWebview"
component={ShopWebview}
/>
<Stack.Screen
name="LogOut"
component={LogOutWebView}
/>
</Stack.Navigator>
// from `AccountDetails` component, user presses handleLogout()
const handleLogOut = () => navigation.navigate('LogOut');
// shopify logout webview
export const LogOutWebView = () => {
return (
<WebView
source={{ uri: 'https://www.quarantotto.co.jp/account/logout' }}
/>
);
};
This seems to work w/ android, but it doesn't seem to work on iOS? I'm not quite sure why that is.
I can still set the incognito to true likeso:
// ShopWebview
<WebView
incognito
source={{ uri: 'someshop.com/' }}
/>
and this would help me not store the logged in user's session, but not quite sure if this is the proper way to handle auth.
If I can use the first method of sending the user to a webview that handles logout when a user visits it, that would be great. But not sure why it's not working from ios.
any ideas?
UPDATE: possible working solution?
Whenever the user logs out, they are sent back to a screen w/ login or register.
The issue was that whenever the user pressed login, since their session was cached, they are redirect to their account screen (which is what I don't want since if a user presses logout, I want them to be logged out and have to log back in).
what I have done is just inject some js in the LoginWebview
const handleNavigationStateChange = (newNavState) => {
if (webViewRef.current) {
webViewRef.current.injectJavaScript(scriptFetchCustomer());
}
if (newNavState.url.includes('logout')) {
setTimeout(() => {
const redirectTo = `window.location = "${someshop.com/login}"`;
webViewRef.current.injectJavaScript(redirectTo);
}, 500);
}
};
seems to work, but I don't enjoy using the settimeout. any recommendations?
second update... doesn't work on android
Android isn't working properly. I've set the source to the shopify's /account/logout uri, but inside handleNavigationStateChange the newNavState's url isn't the logout as it's set on the source.
....
Last update:
It finally works as I wanted to. I had to check for the url changes inside onNavigationStateChange of the webview. Then I could do what I wanted. I basically sent user to /account/logout, then set current logged in user state back into null which fires a redirect to login screen.
Hope it helps someone.

React Native WebView - Open link in external browser only in case of user interaction

I have the following WebView component that is used multiple times for various embeds inside news articles
<WebView
useWebKit={true}
ref={(ref) => this.webview = ref}
javaScriptEnabled={true}
injectedJavaScript={"(" + injectedScript + ")()"}
onMessage={this.onMessage}
onNavigationStateChange={this.onNavigationStateChange}
{...this.props}
/>
and the following navigation state change listener, used for opening links in an external browser
onNavigationStateChange = (event) => {
this.webview.stopLoading();
Linking.openURL(event.url);
}
The problem is that the navigation change listener doesn't seem capable of differentiating between user interaction and automatic page redirects (which social media embeds will often do). How can I open the link in an external browser ONLY for user interaction?
I don't know exactly if automatic page redirects trigger click events, but they should not, so you can listen to only click events, which I suppose can only be triggered by the user.
onNavigationStateChange = (navState) =>
{
if (navState.navigationType === 'click') {
// User clicked something
}
}

How do I display the captcha icon only on certain pages (VUE reCAPTCHA-v3)?

I use this package : https://www.npmjs.com/package/vue-recaptcha-v3
I add on my main.js :
import { VueReCaptcha } from 'vue-recaptcha-v3'
Vue.use(VueReCaptcha, { siteKey: 'xxxxxxx' })
I add this code :
await this.$recaptcha('login').then((token) => {
recaptcha = token
})
to my component to get token from google recapchta
My problem is the captcha icon in the lower right corner appears on all pages
I want it to only appear in certain components
Maybe I must to change this : Vue.use(VueReCaptcha, { siteKey: 'xxxxxxxxxxxxxxxxx' }). Seems it still mounting to Vue.use. I want to mount to a certain component instead of vue root instance
How can I solve this problem?
Update
I try like this :
Vue.use(VueReCaptcha, {
siteKey: 'xxxxxxx',
loaderOptions: {
useRecaptchaNet: true,
autoHideBadge: true
}
})
It hides the badge. I want the badge to still appear. But only on 1 page, the registration page. How can I do it?
I've had the same issue while using the npm package, it's pretty annoying.
At the end of the day, I've decided not to use the package & follow Google's documentation.
This line here :
grecaptcha.execute('_reCAPTCHA_site_key_', {action: 'login'}).then(function(token) {
recaptcha = token
})
Is equivalent to this line here from the npm package :
this.$recaptcha('login').then((token) => {
recaptcha = token
})
You just need to add this line into your < head > for recaptcha to work :
<script src="https://www.google.com/recaptcha/api.js?render=_reCAPTCHA_site_key"></script>
But as soon the script tag is in your < head >, you will be facing the same issue of it showing on every page.
The hack is that you only insert it into the < head > on components that you need.
There are ways to do this but I ended up referencing this.
You can put it in the methods of your component & call the method when the component is loaded.
That way it will only show up on the pages that you need it to.
in main.js set autoHideBadge true:
import { VueReCaptcha } from 'vue-recaptcha-v3'
Vue.use(VueReCaptcha, { siteKey: 'your site key',
loaderOptions:{autoHideBadge: true }})
in every page you want to show the badge you can show the badge in mounted,
for some reasons until a few seconds after mounted event this.$recaptchaInstance is null and you cant use it, so I use a timeout to showing the badge 5 second after page load in mounted.
mounted(){
setTimeout(()=>{
const recaptcha = this.$recaptchaInstance
recaptcha.showBadge()
},5000)
},
when you show it you have to hide it again in the same page.
beforeDestroy() {
const recaptcha = this.$recaptchaInstance
recaptcha.hideBadge()
},
If you are using composition API setup this is what you need:
const reCaptchaIn = useReCaptcha().instance
onMounted(() => {
setTimeout(() => {
reCaptchaIn.value.showBadge()
}, 3000)
})
Just use this code:
const recaptcha = this.$recaptchaInstance
// Hide reCAPTCHA badge:
recaptcha.value.hideBadge()
// Show reCAPTCHA badge:
recaptcha.value.showBadge()
vue-recaptcha-v3 npm
I stumbled upon this incredibly simple answer. It is excellent especially if you wish to hide the badge from all your pages. You can perhaps use scoped css to hide on some pages as well.
.grecaptcha-badge { visibility: hidden; }
You can read the post here

Linking in react native can open just one app

UPDATE 1
I removed return from code and now links work on IOS.
But on android I can't open any app. Any idea?
I am trying to open different apps from my app.
return Linking.openURL(“twitter://“);
return Linking.openURL(“instagram://“);
But it doesn’t work. I configured IOS by documentation. On android doesn’t work too. While...
return Linking.openURL(“tripadvisor://“);
Work just fine.
Any idea why I can’t open other apps.
This is code that I am using (open app if installed or open store with it but sometimes even store doesn't open) what I did wrong:
let appUrl = "instagram://";
Linking.canOpenURL(appUrl).then(supported => {
if (!supported) {
Alert.alert("",
"",
[
{text: "go to store", onPress: this.openStorePress},
{text: "cancel", onPress: () => { }, style: 'cancel'},
],
{ cancelable: false }
);
} else {
return Linking.openURL(appUrl);
}
}).catch(err => {
console.error(err);
});
Your issue is related to the content of the url, twitter:// means nothing for the Android Twitter app, so it will not open.
For example, the following code should work:
Linking.openURL('twitter://timeline')
or
Linking.openURL('instagram://user?username=apple')
You have to find the rights url schemes (documentations are not very clear about it) that may be different between iOS and Android.
Twitter: How can I open a Twitter tweet using the native Twitter app on iOS?
Instagram: https://www.instagram.com/developer/mobile-sharing/iphone-hooks/ (all do not work on Android)
misc: https://pureoxygenlabs.com/10-app-url-schemes-for-marketers/
You have to find the rights URL schemes. Have look at my code
Instagram
Linking.openURL('instagram://user?username=apple')
.catch(() => {
Linking.openURL('https://www.instagram.com/apple');
})
Twitter
Linking.openURL('twitter://user?screen_name=apple')
.catch(() => {
Linking.openURL('https://www.twitter.com/apple');
})
Facebook
Linking.openURL('fb://page/PAGE_ID');
Linking.openURL('http://instagram.com/_u/USER_NAME');
Linking.openURL('http://instagram.com/_p/PICTURE');
Your code looks pretty solid, here's an example of how I open twitter in my app.
const twitterUrlScheme = `twitter://user?screen_name=${twitterUsername}`;
Linking.canOpenURL(twitterUrlScheme)
.then((supported) =>
Linking.openURL(
supported
? twitterUrlScheme
: `https://www.twitter.com/${twitterUsername}`
)
)
.catch((err) => console.error('An error occurred', err));
I think perhaps your issue might be the return Linking.openUrl, I'm not sure you need the return in that statement. Does it work if you remove the return? Otherwise, it might help to move your Alert outside of the then-block from canOpenUrl.
I have used only url and it's working both iOS and android
Linking.openURL('https://www.facebook.com/');
You haven't completed the " fot twitter and instagram, I don't know whether you made the same mistake in app too, if yes, fixing that might solve it.
Try to use a package like:
https://github.com/react-native-community/react-native-share
You can try to use only some of it's functions or look into the native code from there and create some bridge functions in the native code and then export them to be used in JS code.
Note: you will have to use real devices for the tests.

webview onNavigationStateChange returns url of previous page

In react native webview, when I navigate to a page the onNavigationStateChange returns url of the previous page? I think its an issue and posted it to github react native issues.
<WebView
ref={r => this.webview = r}
style = {{marginTop : 0}}
onNavigationStateChange=
{this._onNavigationStateChange.bind(this)}
startInLoadingState = {true}
source = {{uri: 'https://www.youtube.com' }}
/>
_onNavigationStateChange(webViewState)
{
this.setState(
{
youtube_video_url: webViewState.url
})
}
When I print this youtube_video_url, it is of the previous page or previous browsed video.
Sadly, all the webview methods like onNavigationStateChange, onLoad, onLoadStart etc are not working as expected in cases of single page application websites or websites that don't trigger a window.load event.
Normaly onNavigationStateChange gets triggered in the beginning of a request with {loading: true, url: "someUrl", ...} and once after the loading of the url with {loading: false, url: "someUrl", ...}.
There is no global workaround (to my knowledge) that will work correctly for all websites and for both iOs/Android but maybe this can help in your case:
https://snack.expo.io/H1idX8vpM
Basically you inject a custom javascript in the webview that notifies the WebView which is the loaded page. You can enhance it if you want to catch custom window.history manipulation events.