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?
Related
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>
);
};
I've been battling a bug in my code for the last 4 days and would appreciate some pointers to get me going in the right directions. Component is working fine as long as there is internet connection, but if there is no internet connection, audios and videos are not playing, only thumbnail present.
I'm using netInfo's NetInfo.fetch() to check for connection. If there is connection, I'm refetching data to check for any updates to student assignments.
I'm using expo-av for playing audio/video files (v10.2.1). I'm also using useQuery hook from react-query to fetch data about audio and video files (like url etc.) My video player component is something like this:
Video Player:
import React, {
forwardRef,
ForwardRefRenderFunction,
useCallback,
useImperativeHandle,
useRef
} from 'react';
import { Platform } from 'react-native';
import Orientation from 'react-native-orientation-locker';
import { Audio, Video, VideoFullscreenUpdateEvent, VideoProps } from 'expo-av';
const Player: ForwardRefRenderFunction<
Video | undefined,
VideoProps
> = (props, ref) => {
const innerRef = useRef<Video>(null);
const orientation = useCallback<
(event: VideoFullscreenUpdateEvent) => void
>(
(event) => {
if (Platform.OS === 'android') {
if (
event.fullscreenUpdate === Video.FULLSCREEN_UPDATE_PLAYER_DID_PRESENT
) {
Orientation.unlockAllOrientations();
} else if (
event.fullscreenUpdate === Video.FULLSCREEN_UPDATE_PLAYER_DID_DISMISS
) {
Orientation.lockToPortrait();
}
}
props.onFullscreenUpdate?.(event);
},
[props]
);
useImperativeHandle(ref, () => {
if (innerRef.current) {
return innerRef.current;
}
return undefined;
});
return (
<Video
resizeMode="contain"
useNativeControls
ref={innerRef}
onLoad={loading}
{...props}
onFullscreenUpdate={orientation}
/>
);
};
export const VideoPlayer = forwardRef(Player);
Custom Hook:
For async state management, I'm using a custom react-query hook, that looks something like this (non-relevant imports and code removed):
import { useFocusEffect } from '#react-navigation/core';
import { useCallback } from 'react';
import NetInfo from '#react-native-community/netinfo';
export const useStudentAssignment = (
assignmentId: Assignment['id']
): UseQueryResult<Assignment, Error> => {
const listKey = studentAssignmentKeys.list({ assignedToIdEq: studentData?.id });
const queryClient = useQueryClient();
const data = useQuery<Assignment, Error>(
studentAssignmentKeys.detail(assignmentId),
async () => {
const { data: assignment } = await SystemAPI.fetchAssignment(assignmentId);
return Assignment.deserialize({
...assignment,
});
},
{
staleTime: 1000 * 60 * 30,
initialData: () => {
const cache= queryClient.getQueryData<Assignment[]>(listKey);
return cache?.find((assignment) => assignment.id === assignmentId);
},
initialDataUpdatedAt: queryClient.getQueryState(listKey)?.dataUpdatedAt,
}
);
useFocusEffect(
useCallback(() => {
NetInfo.fetch().then((state) => {
if (state.isConnected) {
data.refetch();
}
});
}, [data])
);
return data;
};
Component:
import React, { FC, useCallback, useEffect, useMemo, useRef } from 'react';
import { SafeAreaView } from 'react-native-safe-area-context';
import { StackScreenProps } from '#react-navigation/stack';
import { ROUTES } from 'enums/SMSRoutes';
import { StoreType } from 'enums/SMSStoreType';
import { useStudentAssignment } from 'hooks/Assignments/useStudentAssignment';
import { RootStackParamList } from 'navigators';
import { AssignmentViewer } from 'screens/AssignmentViewer';
type NavProps = StackScreenProps<
RootStackParamList,
ROUTES.ASSIGNMENT_VIEW
>;
export const AssignmentView: FC<NavProps> = ({
navigation,
route: {
params: { assignmentId }
}
}) => {
const assignmentQuery = useStudentAssignment(assignmentId);
const assignmentTracker = useStore(StoreType.AssignmentTracker);
const isDoneRef = useRef<boolean>(false);
const questions = assignmentQuery.data?.questions || [];
const activeQuestion = useMemo(() => {
return questions.filter((question) => question.active);
}, [questions]);
const onDone = useCallback(() => {
isDoneRef.current = true;
navigation.push(ROUTES.ASSIGNMENT_COMPLETED);
}, [navigation]);
useEffect(() => {
assignmentTracker.start(assignmentId);
return () => {
assignmentTracker.finish(isDoneRef.current);
};
}, []);
return (
<SafeAreaView>
<AssignmentViewer
questions={activeQuestion}
onDone={onDone}
isLoading={assignmentQuery.isLoading}
/>
</SafeAreaView>
);
};
What I'm trying to do here is that if internet connection is connected and the user navigates to the current view (which is used to view assignments), I'd like to refetch the data. Per the requirements, I can't use the staleTime property or any other interval based refetching.
Component is working fine if I don't refetch, or if internet connection is present. If connection isn't there, it doesn't play the cache'd audio/video.
If I take out the check for internet connection (remove netInfo), component display videos both offline and online. However, refetching fails due to no connectivity.
What should I change to make sure that data is refetched when connected and videos are played even if not connected to Internet?
Here is my code:, the auth webview open but then when i click allow with my account i can see in console i receive the access token but with an error, i would just like to receive the url in my AuthWebView.js then i can parse it:
Can't open url: epicture://status#access_token=94de672d6210f6ceecfe07efd5d62728c96b8e33&expires_in=315360000&token_type=bearer&refresh_token=cfb46e86a33f4a0cd92e1596335ac21393d13251&account_username=nmateotest&account_id=140192947
code:
import React, { Component } from "react";
import { WebView } from "react-native-webview";
import { Linking } from "react-native";
import { API_URL, API_CLIENT_ID } from "#env";
import APIToken from "./../api/APIToken";
export default class AuthWebView extends Component {
navigate = async (url) => {
if (url) {
console.log(url);
url = url.replace("#", "&");
let params = {};
const regex = /([^&=]+)=([^&]*)/g;
let m;
while ((m = regex.exec(url))) {
parameters[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
}
await APIToken.saveToken("BearerToken", parameters.access_token);
await APIToken.saveToken("username", parameters.account_username);
this.props.navigation.navigate("App");
}
};
componentDidMount() {
Linking.getInitialURL().then((url) => {
this.navigate(url);
});
}
render() {
return (
<WebView
source={{
uri:
API_URL +
"oauth2/authorize?client_id=" +
API_CLIENT_ID +
"&response_type=token",
}}
/>
);
}
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).
I installed the react-navigation package in react-native
I have implemented tab navigation and one of them is implemented in webview format.
My problem is that if I press the back physical button on Android, I go from the app itself to the previous tab, not back from the webview.
I've already applied the back button for the webview on the internet, but I have not done that.
I tried to display the onNavigationStateChange log when debugging, but it was not updated when url was moved after it was loaded at first startup. Here is the code I implemented:
import React from "react";
import {BackHandler} from "react-native";
import {WebView} from "react-native-webview";
class SermonScreen extends React.Component {
constructor(props) {
super(props);
}
static navigationOptions = {
header: null
};
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
_onNavigationStateChange(navState) {
console.log(navState);
this.setState({
canGoBack: navState.canGoBack
});
}
handleBackButton = () => {
console.log(this.state);
if (this.state.canGoBack === true) {
this.webView.goBack();
return true;
} else {
return false;
}
};
render() {
return (
<WebView
source={{uri: 'https://m.youtube.com/channel/UCw3kP3qCCF7ZpLUNzm_Q9Xw/videos' }}
ref={(webView) => this.webView = webView}
onNavigationStateChange={this._onNavigationStateChange.bind(this)}
/>
);
}
}
export default SermonScreen;
Following the official webview documnentation you could try to do this: https://github.com/react-native-community/react-native-webview/blob/master/docs/Guide.md#intercepting-hash-url-changes
In general you were almost there, however the way the YT navigation works made it impossible to be caught via the onNavigationStateChange, that's why we inject a JS code that intercepts these hash changes and posts a message to the parent component, we then catch it inside the onMessage handler and set the state variable properly. Copying the injectedJavaScript and onMessage properties to your example should solve your problem.
I prepared a component for you that seems to do what is needed:
* Sample React Native App
* https://github.com/facebook/react-native
*
* #format
* #flow
*/
import React, { Fragment } from "react";
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
BackHandler,
StatusBar
} from "react-native";
import { WebView } from "react-native-webview";
import {
Header,
LearnMoreLinks,
Colors,
DebugInstructions,
ReloadInstructions
} from "react-native/Libraries/NewAppScreen";
class App extends React.Component {
constructor(props) {
super(props);
this.startingUrl =
"https://m.youtube.com/channel/UCw3kP3qCCF7ZpLUNzm_Q9Xw/videos";
this.handleBackButton = this.handleBackButton.bind(this);
}
componentDidMount() {
BackHandler.addEventListener("hardwareBackPress", this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener("hardwareBackPress", this.handleBackButton);
}
handleBackButton = () => {
console.log(this.state);
const { canGoBack } = this.state;
if (canGoBack) {
this.webView.goBack();
return true;
} else {
return false;
}
};
render() {
return (
<Fragment>
<WebView
source={{ uri: this.startingUrl }}
style={{ marginTop: 20 }}
ref={webView => (this.webView = webView)}
injectedJavaScript={`
(function() {
function wrap(fn) {
return function wrapper() {
var res = fn.apply(this, arguments);
window.ReactNativeWebView.postMessage('navigationStateChange');
return res;
}
}
history.pushState = wrap(history.pushState);
history.replaceState = wrap(history.replaceState);
window.addEventListener('popstate', function() {
window.ReactNativeWebView.postMessage('navigationStateChange');
});
})();
true;
`}
onMessage={({ nativeEvent: state }) => {
if (state.data === "navigationStateChange") {
// Navigation state updated, can check state.canGoBack, etc.
this.setState({
canGoBack: state.canGoBack
});
}
}}
/>
</Fragment>
);
}
}
export default App;
The response above was perfect. I set the state true for canGoBack though; I was getting a null error, so:
constructor(props) {
super(props);
this.startingUrl = "https://app.vethorcardpag.com.br/GIF/login/0/";
this.state = {
canGoBack : true
}
this.handleBackButton = this.handleBackButton.bind(this);
}
Here is a simple solution using the magic of React's State.
Hope this helps.
import React, { useRef, useState } from 'react'
export default function Component () {
// This is used to save the reference of your webview, so you can control it
const webViewRef = useRef(null);
// This state saves whether your WebView can go back
const [webViewcanGoBack, setWebViewcanGoBack] = useState(false);
const goBack = () => {
// Getting the webview reference
const webView = webViewRef.current
if (webViewcanGoBack)
// Do stuff here if your webview can go back
else
// Do stuff here if your webview can't go back
}
return (
<WebView
source={{ uri: `Your URL` }}
ref={webViewRef}
javaScriptEnabled={true}
onLoadProgress={({ nativeEvent }) => {
// This function is called everytime your web view loads a page
// and here we change the state of can go back
setWebViewcanGoBack(nativeEvent.canGoBack)
}}
/>
)
}
Original answer
https://stackoverflow.com/a/74500469/7823800