React Native - Set localStorage in WebView - reactjs

I would like to set localStorage in the WebView component before loading the page.
https://facebook.github.io/react-native/docs/webview
My use case for this is that my RN app would like to open a page on it's accompanying website. The website authenticates on load by checking for a token in localStorage. If the token isn't there they will be prompted to login. Seen as the user has already logged in on the app I would prefer it if they could avoid logging in again in the WebView.

You might have overcome the issue. If not here is my solution.
You can use injectedJavaScript property of WebView and add javascript code to add the token on page load.
Example code snippet.
My Custom Javascript code that i need to inject.
let myInjectedJs = `(function(){ let tk = window.localStorage.getItem('tokenKey');
if(!tk || (tk && tk != '${token}')){
window.localStorage.setItem('tokenKey', '${token}');
window.location.reload();
}
})();`;
Code Explanation
Function is called as soon as loaded. Checks if tokenKey is already set. If not set we are setting it to the new token ${token} and reloading the page.
Note: We need to pass function as a string to injectedJavaScript in webview.
Using myInjectedJs in WebView.
<WebView
ref={webView => { this.refWeb = webView; }}
javaScriptEnabled={true}
injectedJavaScript={myInjectedJs}
...
Hope this solves your problem.

injectedJavaScript and window.ReactNativeWebView.postMessage should do the job.
import { WebView } from 'react-native-webview';
const INJECTED_JAVASCRIPT = `(function() {
const tokenLocalStorage = window.localStorage.getItem('token');
window.ReactNativeWebView.postMessage(tokenLocalStorage);
})();`;
export default function App() {
const onMessage = (payload) => {
console.log('payload', payload);
};
return (
<View style={styles.container}>
<StatusBar style="auto" />
<WebView
source={{ uri: 'https://somewebsite.com/login' }}
injectedJavaScript={INJECTED_JAVASCRIPT}
onMessage={onMessage}
/>
</View>
);
}
Payload data:
Object {
"nativeEvent": Object {
"canGoBack": true,
"canGoForward": false,
"data": "your_localstorage_value",
"loading": false,
"target": 3,
"title": "Coil",
"url": "https://somewebsite.com/home",
},
}
Works on both Android and iOS.

Related

Sha1 in Google API in React Native

I'm a bit confused on generating SHA1 to access the Google cloud API. I have been developing an App which retrieve or display data after you login your google email, I've already used the clientID from the Google platform and paste it to my code but it doesn't work there's no error in my react mobile and I think the problem is the setup of my google API but I've already created one.
Most resources suggest to generate the `SHA1' by using the command below
keytool -list -v -keystore ./android/App/debug.keystore -alias androiddebugkey -storepass android -keypass android
Yet I see the result which generate the SHA1 but it's confusing me on how to used the generated SHA1 to google API
should I add SHA1 to google Platform console? how?
The comment/image below describes the solution to my project but I didn't understand at all
App.js
import React, { Component } from 'react';
import { View, StyleSheet, ToastAndroid, Button, Text, Image } from "react-native";
import {
GoogleSignin,
GoogleSigninButton,
statusCodes,
} from '#react-native-community/google-signin';
GoogleSignin.configure({
webClientId: '174................................9328.apps.googleusercontent.com', //sample
offlineAccess: true, // if you want to access Google API on behalf
});
class App extends Component {
constructor(props) {
super(props)
this.state = {
userGoogleInfo: {},
loaded: false
}}
signIn = async () => {
try {
console.log("asdsad");
await GoogleSignin.hasPlayServices();
const userInfo = await GoogleSignin.signIn();
this.setState({
userGoogleInfo: userInfo,
loaded: true
})
console.log(this.state.userGoogleInfo);
console.log("ok");
} catch (error) {
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
console.log("e 1");
} else if (error.code === statusCodes.IN_PROGRESS) {
console.log("e 2");
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
console.log("e 3");
} else {
console.log(error.message);
console.log("errrorr");
}}
};
render() {
return (
<View>
<GoogleSigninButton
style={{ width: 222, height: 48 }}
size={GoogleSigninButton.Size.Wide}
color={GoogleSigninButton.Color.Dark}
onPress={this.signIn}
/>
{this.state.loaded ?
<View>
<Text>{this.state.userGoogleInfo.user.name}</Text>
<Text>{this.state.userGoogleInfo.user.email}</Text>
<Image
style={{ width: 100, height: 100 }}
source={{ uri: this.state.userGoogleInfo.user.photo }}
/>
</View>
: <Text>Not SignedIn</Text>}
</View>
);}}
export default App;
Follow below steps for add SHA1 key in your google cloud console
you need to login your google cloud console when you create project https://console.cloud.google.com/apis/credentials?authuser=1&project=<your_project_id>
Then select your project and choose Credentials left bar menu
Right side API key section and click on API key which you want to
add SHA1 and add your SHA1 key which you generated from command
click on SAVE button below within dew minutes your app will work
Refer below attachment which show where you need to place SHA1 key in google cloud console project

How to use PostMessage in a React Native webview?

I'm trying to use the postmessage to a page opened in a webview inside a React Native App. I tried many times, but still wasn't able to send it.
I can listen to messages from the webpage normally. I just can't send anything back.
I'm currently using react-native-webview 11.6.5
export default function WebPage() {
const webviewRef = useRef();
const onMessage = (event) => {
//receive message from the web page. working here until here
const data = JSON.parse(event.nativeEvent.data);
//reply the message
webviewRef.current.postMessage(
JSON.stringify({reply: 'reply'}),
'*'
)
}
return <View>
<WebView
ref={webviewRef}
originWhitelist={['*']}
source={{ uri: 'https://robertnyman.com/html5/postMessage/postMessage.html' }}
domStorageEnabled
javaScriptEnabled
onMessage={onMessage}
/>
</View>
}
Any ideas what am I doing wrong?
UPDATE:
Thanks to #Ahmed Gaber help I was able to find this issue https://github.com/react-native-webview/react-native-webview/issues/809 and discover they're changing postMessage to injectJavaScript.
So I updated my code onMessage to the following:
const onMessage = (event) => {
const data = JSON.parse(event.nativeEvent.data);
//reply the message
webviewRef.current.injectJavaScript(
`window.postMessage(
{
reply: 'reply'
}
);`
)
}
To send data from app to webview, use injectedJavaScript
To send data from webview to app, use postMessage
To receive data in webview sent by postMessage, use onMessage
// This Js function will be injected into the web page after the document finishes loading.
// This function will Post a message to WebView.
const INJECTED_JAVASCRIPT = `(function() {
window.ReactNativeWebView.postMessage(JSON.stringify({key : "value"}));
})();`;
<WebView
source={{ uri: 'https://reactnative.dev' }}
injectedJavaScript={INJECTED_JAVASCRIPT}
onMessage={(event) => {
const data = JSON.parse(event.nativeEvent.data);
alert(data.key);
}}
/>;
React native code
function getInjectableJSMessage(message) {
return `
(function() {
document.dispatchEvent(new MessageEvent('message', {
data: ${JSON.stringify(message)}
}));
})();
`;
}
function sendDataToWebView() {
webviewRef.current?.injectJavaScript(
getInjectableJSMessage("Hello")
);
}
React web app code
React.useEffect(() => {
function handleEvent(message) {
console.log(message.data);
}
document.addEventListener("message", handleEvent);
return () =>
document.removeEventListener("message", handleEvent);
}, []);

react-pdf: use PDFDownloadLink asynchronously without blocking the rest of the application

I'm using PDFDownloadLink from the react-pdf package to generate a PDF on the fly in my application and allow the user to download a report based on data being passed to the component that generates the PDF document. However, there are more than 400 pages that need to be rendered in this PDF, and this operation blocks the main thread for a few seconds. Is there any way to make this operation asynchronous, so the rest of the application will continue to function while the PDF is being generated? Also I would like to be able to cache the results, since the data being passed to the component can come from about 8 different arrays of data, which don't change very much, so switching between these arrays I would rather not to have to render the PDF all over again if the PDF for that given array has already been generated once before... I'm guessing the blob data needs to be stored somewhere, perhaps localStorage?
import { Page, Text, View, Document, StyleSheet, PDFDownloadLink } from '#react-pdf/renderer'
const App = () => {
const condition = "firstCondition";
const filteredRowData = rowData.filter(a => a.condition = condition);
return (
<PDFDownloadLink
document={<PDF_REPORT_Document rowData={filteredRowData} />}
fileName={"PDF_REPORT.pdf"}
style={{color:'white'}}
>{({ blob, url, loading, error }) =>
loading ? "Report loading..." : "Report ready to download"
}</PDFDownloadLink>
);
}
const PDF_REPORT_Document = (props) => {
const { rowData } = props;
const styles = StyleSheet.create({
page: {
flexDirection: 'column',
backgroundColor: '#E4E4E4'
},
section: {
margin: 10,
padding: 10,
flexGrow: 1
}
});
return(
<Document>
{rowData.map((row,index) =>
<Page size="A4" style={styles.page} key={index}>
<View style={styles.section}>
<Text>Name: {row.FULLNAME}</Text>
</View>
</Page>
)}
</Document>
);
}
I finally found the answer to this in an issue on github which addresses this exact problem:
Is your feature request related to a problem? Please describe.
It is an improvement. At the moment, if you use 'PDFDownloadLink' the PDF is being generated as the component loads.
Describe the solution you'd like
It is not mandatory, but having multiple heavy PDFs ready to be downloaded wouldn't be the best approach since not every user will need it.
Describe alternatives you've considered
I've used pdf() function to generate the blob and file-saver lib to download it:
import { saveAs } from 'file-saver';
import { pdf } from '#react-pdf/renderer';
import PdfDocument from '../PdfDocument';
const generatePdfDocument = async (documentData,fileName) => {
const blob = await pdf((
<PdfDocument
title='My PDF'
pdfDocumentData={documentData}
/>
)).toBlob();
saveAs(blob, fileName);
};
export default generatePdfDocument;

How to refresh powerbi-client-react component once token has expired?

I am testing a react web app where I can display reports from Power BI. I am using powerbi-client-react to embed the reports. However, I face an issue when I load the component with an expired token, I get this error: Content not available screenshot.
So whenever that happens, I catch it with the error event handler, get a new token and update the powerbi report accessToken. However, it doesn't seem to reload/refresh the embed when I set the new accessToken in react. It only displays the screenshot above.
Error log screenshot.
Is there a way to force refresh the embed component with the new access token? or is my approach not correct? Any mistakes pointer would be appreciated.
import React from 'react';
import {models} from 'powerbi-client';
import {PowerBIEmbed} from 'powerbi-client-react';
// Bootstrap config
let embedConfigTest = {
type: 'report', // Supported types: report, dashboard, tile, visual and qna
id: reportId,
embedUrl: powerBIEmbedURL,
accessToken: null,
tokenType: models.TokenType.Embed,
pageView: 'fitToWidth',
settings: {
panes: {
filters: {
expanded: false,
visible: false,
},
},
background: models.BackgroundType.Transparent,
},
};
const PowerBiReport = ({graphName, ...props}) => {
let [embedToken, setEmbedToken] = React.useState();
let [embedConfig, setEmbedConfig] = React.useState(embedConfigTest);
React.useEffect(
() => {
setEmbedToken(EXPIRED_TOKEN);
setEmbedConfig({
...embedConfig,
accessToken: EXPIRED_TOKEN, // Initiate with known expired token
});
},
[graphName]
);
const changeSettings = (newToken) => {
setEmbedConfig({
...embedConfig,
accessToken: newToken,
});
};
// Map of event handlers to be applied to the embedding report
const eventHandlersMap = new Map([
[
'loaded',
function() {
console.log('Report has loaded');
},
],
[
'rendered',
function() {
console.log('Report has rendered');
},
],
[
'error',
async function(event, embed) {
if (event) {
console.error(event.detail);
console.log(embed);
// Simulate getting a new token and update
setEmbedToken(NEW_TOKEN);
changeSettings(NEW_TOKEN);
}
},
],
]);
return (
<PowerBIEmbed
embedConfig={embedConfig}
eventHandlers={eventHandlersMap}
cssClassName={'report-style-class'}
/>
);
};
export default PowerBiReport;
Thanks #vtCode. Here is a sample but the refresh can only happen in 15 secs interval.
import { PowerBIEmbed } from "powerbi-client-react";
export default function PowerBiContainer({ embeddedToken }) {
const [report, setReport] = useState(null);
useEffect(() => {
if (report == null) return;
report.refresh();
}, [report, embeddedToken]);
return (
<PowerBIEmbed
embedConfig={{ ...embedConfig, accessToken: embeddedToken }}
getEmbeddedComponent={(embeddedReport) => setReport(embeddedReport)};
/>
);
}
Alternatively, you can add the React "key" attribute which remounts the component when embededToken changes
<PowerBIEmbed key={embeddedToken}
embedConfig={{ ...embedConfig, accessToken: embeddedToken }}
/>
I ended up solving this issue, although not so beautiful.
I checked the powerbi-client wiki as it has dependency on it and found out that you could use embed.reload() in the embed object I get from the error function.
For some reason (I could not find out why), the error handler gets triggered twice, so to avoid refreshing the token twice, I had to create a dialog notifying the user that the token had expired and whenever that dialog is closed, I reload the powerbi report.
Exact wiki reference:
Overriding Error Experience
Reload a report
Update embed token

Electron in production mode pointing URL calls to file URL instead of proxy URL

I have created a simple application using React and Electron that handles few requests, the electron app works fine on dev mode, but during production, there is an issue, all the API calls, written in the code are pointing to file URL: /C:/... instead of proxy mapping to localhost.
Code Snippet is as:
export default class App extends Component {
state = { username: null };
componentDidMount() {
fetch('/api/getUsername')
.then(res => res.json())
.then(user => this.setState({ username: user.username }));
}
render() {
const { username } = this.state;
return (
<div>
<img src={ReactImage} alt="react" style={{ height: '250px' }} />
{username ? <h1>{`Hello there, I'm ${username}`}</h1> : <h1>Loading.. please wait!</h1>}
<Link to="/users">See More users</Link>
</div>
)
}
}
So I have no idea why this is happening, so it would of great help if anyone knows how to resolve it.
Thanks in advance!!

Resources