Use RN Expo Web Browser dismissBrowser when on specific URL - react-native

Trying to achieve the following scenario
Client clicks a button and it opens the Expo web browser with a URL e.g wwww.example.com/test
User does something and is redirected to a URL like wwww.example.com/success
The app recognizes the URL and auto-closes the web browser
It correctly opens the web browser but nothing happens afterward when I go to wwww.example.com/success.
I'm not getting any errors and with the iOS Expo preview, I get no console.log triggers, but with the Web Expo preview, I get generic logging.
Code below
import React, { useState } from 'react';
import { Button, StyleSheet, Text, View } from 'react-native';
import * as Linking from 'expo-linking';
import * as WebBrowser from 'expo-web-browser';
export const WebVerification = () => {
const [redirectData, setRedirectData] = useState(null);
const _handleRedirect = (event) => {
console.log('handle Request is called')
let data = Linking.parse(event.url);
console.log(data)
if( data.url === "wwww.example.com/success") {
WebBrowser.dismissBrowser();
}
setRedirectData(data);
};
const _openBrowserAsync = async () => {
try {
_addLinkingListener();
let result = await WebBrowser.openBrowserAsync(
`wwww.example.com/test`
);
} catch (error) {
alert(error);
console.log(error);
}
};
const _addLinkingListener = () => {
Linking.addEventListener("url", _handleRedirect);
};
const _removeLinkingListener = () => {
Linking.removeEventListener("url", _handleRedirect);
};
const _maybeRenderRedirectData = () => {
console.log("check RenderRedirect" + redirectData)
if (!redirectData) {
return;
}
return (
<Text style={{ marginTop: 30 }}>
{JSON.stringify(redirectData)}
</Text>
);
};
return (
<View>
<Text>Redirect Example</Text>
<Button onPress={_openBrowserAsync} title="Open Browser" />
{_maybeRenderRedirectData()}
</View>
);
};

Related

How to handle Auth with WebBrowser.openAuthSessionAsync in Expo?

I created a auth flow using WebBrowser.openAuthSessionAsync, the opening and the closing are working as expected but my problem comes with the return, I only receive back from the browser: {"type": "success", "url": "exp://192.168.0.11:19000/--/App"}, the type and my return URL.
I'm trying to figure out how to have access to any kind of info from the return, from cookies to query string on the URL as I have control over how my API handles the return, but I'm not being able to access any kind of info from the AuthSession.
Here is the implementation:
import { useEffect, useState } from "react"
import { Button, View, Text } from "react-native"
import * as WebBrowser from "expo-web-browser"
import * as Linking from "expo-linking"
export default () => {
const [result, setResult] =
useState<WebBrowser.WebBrowserAuthSessionResult | null>(null)
useEffect(() => {
console.log(result)
}, [result])
const _handlePressButtonAsync = async () => {
const baseUrl = "https://...com"
const callbackUrl = Linking.createURL("App", { scheme: "myapp" })
setResult(
await WebBrowser.openAuthSessionAsync(
`${baseUrl}/login?returnUrl=${encodeURIComponent(
`${baseUrl}/_v/login?token=...&iv=...&returnUrl=${callbackUrl}`
)}`,
callbackUrl
)
)
}
return (
<View className="items-center justify-center flex-1">
<Button title="Open Auth Session" onPress={_handlePressButtonAsync} />
{result && <Text>{JSON.stringify(result)}</Text>}
</View>
)
}
It's a SASS platform, that does not follow only the OAuth flow, which made me choose this method over https://docs.expo.dev/versions/latest/sdk/auth-session/

How to download a pdf file in a react-native iOS webview?

Just developing a simple react-native app using expo and react-native-webview library.
The problem is that when users try to download an invoice in pdf format, iOS shows the preview and it's not possible to go back to the app.
Here attached the main app screen component:
import React, { useState } from "react";
import { ActivityIndicator, Share, StyleSheet } from "react-native";
import * as FileSystem from 'expo-file-system';
const { downloadAsync, documentDirectory } = FileSystem;
import { SafeAreaView } from 'react-native-safe-area-context';
import { WebView } from 'react-native-webview';
const HomeScreen = ({ navigation }) => {
const [loading, setLoading] = useState(true);
let downloadDocument = async (downloadUrl) => {
alert('downloadUrl 2: ', downloadUrl);
let fileURI = await downloadAsync(
downloadUrl,
`${documentDirectory}/invoice.pdf`,
{}
);
await onShare(fileURI.uri);
}
const onShare = async (url) => {
try {
return Share.share({
message: 'Select storage location',
url: url
});
} catch (error) {
alert('error: ', error);
return error;
}
};
return (
<SafeAreaView style={styles.container}>
<WebView
source={{ uri: '<url>' }}
onError={() =>
navigation.navigate('Error')
}
setSupportMultipleWindows={false}
startInLoadingState={true}
renderLoading={() =>
<ActivityIndicator
style={styles.spinner}
size='large'
color='#0098D4'
/>
}
domStorageEnabled={true}
// iOS
onFileDownload={({ nativeEvent: { downloadUrl } }) => {
alert('downloadUrl: ', downloadUrl);
downloadDocument(downloadUrl);
}}
/>
</SafeAreaView>
);
}
We added some alerts, but the're never fired.
In the html code, there is an tag with href property pointing to the file's url and the download option set.
Any solution?

react-native WebView session after closing IOS application

I have a simple react native application with a WebView component for displaying my website based on php. So it works. But, when I login and close the application (on IOS), the session state was loose and I need login again. I know that all webview data like cookies cleared when the application close. So I need AsyncStorage to save all cookies to application storage and get it when application open for passing to headers.
Here's my code:
import React, {Component} from 'react';
import AsyncStorage from '#react-native-community/async-storage';
import CookieManager from '#react-native-community/cookies';
import {SafeAreaView, StyleSheet, Alert} from 'react-native';
import {WebView} from 'react-native-webview';
let domain = "[https]://mysite.com";
export default class WebViewScreen extends Component {
constructor(props) {
super(props);
this.currentUrl = domain;
this.myWebView = React.createRef();
this.state = {
isReady: false,
cookiesString: '',
};
}
jsonCookiesToCookieString = (json) => {
let cookiesString = '';
for (let [key, value] of Object.entries(json)) {
cookiesString += `${key}=${value.value}; `;
}
return cookiesString;
};
UNSAFE_componentWillMount() {
this.provideMeSavedCookies()
.then(async (savedCookies) => {
let cookiesString = this.jsonCookiesToCookieString(savedCookies);
const PHPSESSID = await AsyncStorage.getItem('PHPSESSID');
if (PHPSESSID) {
cookiesString += `PHPSESSID=${PHPSESSID};`;
}
this.setState({cookiesString, isReady: true});
})
.catch((e) => {
this.setState({isReady: true});
});
}
onLoadEnd = (syntheticEvent) => {
let successUrl = `${domain}/dashboard.php`;
if (this.currentUrl === successUrl) {
CookieManager.getAll(true).then((res) => {
AsyncStorage.setItem('savedCookies', JSON.stringify(res));
if (res.PHPSESSID) {
AsyncStorage.setItem('PHPSESSID', res.PHPSESSID.value);
}
});
}
};
onNavigationStateChange = (navState) => {
this.currentUrl = navState.url;
};
provideMeSavedCookies = async () => {
try {
let value = await AsyncStorage.getItem('savedCookies');
if (value !== null) {
return Promise.resolve(JSON.parse(value));
}
} catch (error) {
return {}
}
};
render() {
const {cookiesString, isReady} = this.state;
if (!isReady) {
return null;
}
return (
<SafeAreaView>
<WebView
ref={this.myWebView}
source={{
uri: domain,
headers: {
Cookie: cookiesString,
},
}}
allowsBackForwardNavigationGestures
originWhitelist={['*']}
scalesPageToFit
useWebKit
onLoadEnd={this.onLoadEnd}
onNavigationStateChange={this.onNavigationStateChange}
sharedCookiesEnabled={true}
javaScriptEnabled={true}
domStorageEnabled={true}
/>
</SafeAreaView>
);
}
}
I missed all styles for most clearly and understanding code
So, I login and close the application. Then open again. It works only for one request, but not for multiple or when navigation. When I'm trying go to another page I loose my session and redirect to login page. If I change domain variable from mysite.com to mysite.com/my-profile where /my-profile page only for authorized users, it works too. My headers work only for first request page. How to fix it? How save the session after closing the app for each pages, not only for domain value?

React Native Navigate To Another Screen Automatically

I am trying to navigate back to the LogIn screen as soon as logout.js is hit.
At one point this seemed to work and then stopped and I can't work out why.
Here is my code:
import React, { Component } from 'react';
import { Text, View, Button } from 'react-native';
import * as SecureStore from 'expo-secure-store';
import { globalStyles } from '../styles/global';
export default class App extends Component {
componentDidMount() {
(async () => {
const token = await SecureStore.getItemAsync('token');
// Your fetch code
this.setState({loaded:false, error: null});
let url = 'https://www.example.com/api/auth/logout';
let h = new Headers();
h.append('Authorization', `Bearer ${token}`);
h.append('Content-Type', 'application/json');
h.append('X-Requested-With', 'XMLHttpRequest');
let req = new Request(url, {
headers: h,
method: 'GET'
});
fetch(req)
.then(response=>response.json())
//.then(this.showData)
.catch(this.badStuff)
})();
this.deleteToken()
}
badStuff = (err) => {
this.setState({loaded: true, error: err.message});
}
deleteToken() {
(async () => {
const token = await SecureStore.deleteItemAsync('token')
});
this.goToLogInScreen()
}
goToLogInScreen() {
this.props.navigation.navigate('LogIn')
}
render() {
return (
<View style={globalStyles.container}>
<Button
title="Go to Details"
onPress={() => this.props.navigation.navigate('LogIn')}
/>
</View>
);
}
}
I need the code to:
Send the command to the API to log out
Delete the token from SecureStore
Navigate to the LogIn screen (containing the log in form).

Lodash debounce not working all of a sudden?

I'm using a component I wrote for one app, in a newer app. The code is like 99% identical between the first app, which is working, and the second app. Everything is fine except that debounce is not activating in the new app. What am I doing wrong?
// #flow
import type { Location } from "../redux/reducers/locationReducer";
import * as React from "react";
import { Text, TextInput, View, TouchableOpacity } from "react-native";
import { Input } from "react-native-elements";
import { GoogleMapsApiKey } from "../../.secrets";
import _, { debounce } from "lodash";
import { connect } from "react-redux";
import { setCurrentRegion } from "../redux/actions/locationActions";
export class AutoFillMapSearch extends React.Component<Props, State> {
textInput: ?TextInput;
state: State = {
address: "",
addressPredictions: [],
showPredictions: false
};
async handleAddressChange() {
console.log("handleAddressChange");
const url = `https://maps.googleapis.com/maps/api/place/autocomplete/json?key=${GoogleMapsApiKey}&input=${this.state.address}`;
try {
const result = await fetch(url);
const json = await result.json();
if (json.error_message) throw Error(json.error_message);
this.setState({
addressPredictions: json.predictions,
showPredictions: true
});
// debugger;
} catch (err) {
console.warn(err);
}
}
onChangeText = async (address: string) => {
await this.setState({ address });
console.log("onChangeText");
debounce(this.handleAddressChange.bind(this), 800); // console.log(debounce) confirms that the function is importing correctly.
};
render() {
const predictions = this.state.addressPredictions.map(prediction => (
<TouchableOpacity
style={styles.prediction}
key={prediction.id}
onPress={() => {
this.props.beforeOnPress();
this.onPredictionSelect(prediction);
}}
>
<Text style={text.prediction}>{prediction.description}</Text>
</TouchableOpacity>
));
return (
<View>
<TextInput
ref={ref => (this.textInput = ref)}
onChangeText={this.onChangeText}
value={this.state.address}
style={[styles.input, this.props.style]}
placeholder={"Search"}
autoCorrect={false}
clearButtonMode={"while-editing"}
onBlur={() => {
this.setState({ showPredictions: false });
}}
/>
{this.state.showPredictions && (
<View style={styles.predictionsContainer}>{predictions}</View>
)}
</View>
);
}
}
export default connect(
null,
{ setCurrentRegion }
)(AutoFillMapSearch);
I noticed that the difference in the code was that the older app called handleAddressChange as a second argument to setState. Flow was complaining about this in the new app so I thought async/awaiting setState would work the same way.
So changing it to this works fine (with no flow complaints for some reason. maybe because I've since installed flow-typed lodash. God I love flow-typed!):
onChangeText = async (address: string) => {
this.setState(
{ address },
_.debounce(this.handleAddressChange.bind(this), 800)
);
};