(React Native) Load local HTML file into WebView - webview

I try to load the local .html file into WebView in React Native:
// load local .html file
const PolicyHTML = require('./Policy.html');
// PolicyHTML is just a number of `1`
console.log('PolicyHTML:', PolicyHTML);
// will cause an error: JSON value '1' of type NSNumber cannot be converted to NSString
<WebView html={PolicyHTML} />
The .html file should be read as a string, not as a resource representative.
How can I load the .html file into WebView in React Native?
By the way, what is the type of those resource representatives from require()? Is it number?

try it:
const PolicyHTML = require('./Policy.html');
<WebView
source={PolicyHTML}
style={{flex: 1}}
/>

I come across this post searching for loading static html.
If your html code is retrieved using, for example, an API, you can render WebView in this way:
<WebView
originWhitelist={['*']}
source={{ html: html, baseUrl: '' }}
/>
Notice that originWhitelistis required as explained in the documentation:
Note that static html will require setting of originWhitelist for
example to ["*"].

<View style={{flex: 1}}>
<WebView
style={{flex: 1}}
source={require("./resources/index.html")}
/>
</View>
To make WebView, the parent has to has a dimension or flex:1. We could set the WebView to flex: 1 too so that it fills up the parent.

If you need to serve local assets as well, then:
put all assets together with index.html into android/app/src/main/assets/www (You can copy them there with gradle task)
Then:
var uri = Platform.OS == "android" ?
"file:///android_asset/www/index.html" :
"./web/www/index.html"
return <WebView source={{ uri }} />
** For iOS didn't tested, please add instruction, how assets should be stored

With Expo tools and generally using Expo:
import { WebView } from "react-native-webview";
import { readAsStringAsync } from "expo-file-system";
import { useAssets } from "expo-asset";
export const MusicSheet = () => {
const [index, indexLoadingError] = useAssets(
require("../assets/musicsheetview/index.html")
);
const [html, setHtml] = useState("");
if (index) {
readAsStringAsync(index[0].localUri).then((data) => {
setHtml(data);
});
}
return (
<View style={styles.container}>
<WebView
onLoad={() => {}}
source={{ html }}
onMessage={(event) => {}}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
height: 100,
display: "flex",
},
});

Try this :
Add your .html file in your project.
Write such lines of code in the file where you want to use WebView Component
const OurStoryHTML = require ('./OurStory.html')
<WebView
source={OurStoryHTML}
style={{flex: 1, marginTop : 44}}
/>
It may help you.

If you're working with assets, project directories is different on the device's directory once the project is build and you can't simply reference them via string url.
Expo
If using expo, you have to require every asset then use useAssets on the require to cache them to the local storage of the device.
useAssets will return an object that contains a localUri
(this is the uri of the image that has been cached)
you can then use the localUri and put it as the src of the image
import { useAssets } from 'expo-asset';
/* . . . */
const IndexHTML = require('./assets/index.html');
const myImage = require('./assets/splash.png');
// url link after image is cached to the device
const [imgSrc, setImgSrc] = useState('');
const [image, imerr] = useAssets(myImage);
const [html, error] = useAssets(IndexHTML);
const webViewProps = {
javaScriptEnabled: true,
androidLayerType: 'hardware',
originWhitelist: ['*'],
allowFileAccess: true,
domStorageEnabled: true,
mixedContentMode: 'always',
allowUniversalAccessFromFileURLs: true,
onLoad: () => {
console.log(image[0].localUri);
setImgSrc(image[0].localUri);
},
source: {
html: '<img src="' + imgSrc + '"/>',
},
};
return <WebView {...webViewProps} />
const webViewProps = {
...
source: IndexHTML,
};
Note: for the expo apporach, files referenced in IndexHTML will not be found
The trick is to turn your html into a string literal to utilize template strings.
Then you have to manually require each of those assets to concatenate localUrl
require() has limited types supported and you need to add a metro.config.js in your root folder.
it will give errors if you require() a .js file since it reads it as a module rather, the workaround approach would be to bundle your assets
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.resolver.assetExts.push(
// Adds support for asset file types
'css', 'ppt', 'obj'
);
module.exports = config;
Moreover, expo can hot reload changes done with the cached assets.
React Native
If you have the android folder in your directory, navigate to
android > app > build.gradle
then add
android {
/* link assets to the local storage of device */
sourceSets {
main { assets.srcDirs = ['src/main/assets', '../../source/assets/'] }
// [do not touch, 'relative to your asset'] }
}
. . .
finally, the relative folder you linked in gradle can be accessed through
file:///android_asset/
for ex. file:///android_asset/index.html -> /asset/index.html
return <WebView source={{uri: `file:///android_asset/index.html`}} />
For IOS, here's how
On the other hand, you have to rebuild vanilla react to see the changes in the assets.. which takes about 20 minutes or so
Alternative
A quick solution would be to integrate a static server, but this is a recent fork of the react-native-static-server that only works in vanilla react native.

Related

React Native - Webview set cookies policy

To give you context, our app is a react-native web wrapper (something similar to what Cordova or ionic does).
The app downloads an updated version of the web bundle that is stored locally after the user opens the app, and then the WebView component uses it as a source to show the app to the user.
There are some parts of this web app that uses web sockets, so the issue is that considering its large traffic, the socket/exchange service has a bunch of nodes/replicas and in order to keep connections alive the load balancer uses Set-Cookie headers to make sure that the client is going to the right service node in every request
So, what we're trying is:
Enable Set-Cookie behavior within a WebView
Get Set-Cookie response header manually from a request that is happening within a WebView
Our react-native application looks like:
import React from 'react';
import {Platform, StyleSheet, View} from 'react-native';
import {WebView} from 'react-native-webview';
type WrapperProps = {
path: string;
};
function Wrapper({path}: WrapperProps) {
return (
<View style={styles.container}>
<WebView
allowFileAccess
allowUniversalAccessFromFileURLs
allowingReadAccessToURL={path}
javaScriptEnabled
originWhitelist={['*']}
sharedCookiesEnabled
source={{
uri:
Platform.OS === 'ios'
? `file://${path}/index.html`
: `${path}/index.html`,
}}
injectedJavaScript={`(function () {
window.ReactNativeWebView.postMessage(JSON.stringify(document.coookie));
var open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url) {
this.addEventListener("load", function () {
var responseHeaders = this.getAllResponseHeaders();
var setCookies = this.getResponseHeader("Set-Cookie"); // always return null
window.ReactNativeWebView.postMessage(JSON.stringify({ setCookies }));
window.ReactNativeWebView.postMessage(
JSON.stringify({ responseHeaders }) // return all headers except for Set-Cookie
);
});
open.apply(this, arguments);
};
})();
`}
onMessage={e => {
console.log({onMessageEvent: e.nativeEvent.data});
}}
style={styles.webview}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
webview: {
height: '100%',
width: '100%',
},
});
export default React.memo(Wrapper);
Do you have any clue how to solve this issue?
P.S: We already read How to set WKWebView cookie to accept Policy - But it's for swift.
I managed to
Enable Set-Cookie behavior within a WebView
by establishing a socket connection using socket.io-client, so the web socket requests within the WebView will already include cookies in request headers (which will keep connections alive).
Considering react-native-webview uses WKWebView on iOS, there is no way to access cookies in response headers. However, in this particular scenario you can create the socket connection from outside the web view, handle cookies (it seems socket.io-client already does), and then they should be available in the requests within the WebView.
In order to setup socket io in your react-native app, you will need to
Install socket.io-client#2.1.1 (I tried different versions but this was the only one that worked)
Assign window.navigator.userAgent = 'react-native'; (since ES6 modules are hoisted, this assignment must not be done in the same file as react-native and socket io imports)
Establish your socket connection.
So it should look like:
// userAgent.js
window.navigator.userAgent = 'react-native';
// Wrapper.tsx
import React from 'react';
import {Platform, StyleSheet, View} from 'react-native';
import {WebView} from 'react-native-webview';
// user agent MUST be imported before socket io
import './userAgent';
import io from 'socket.io-client';
export const socket = io('https://your.host.com/', {
path: '/path/to/socket.io/',
jsonp: false,
});
type WrapperProps = {
path: string;
};
function Wrapper({path}: WrapperProps) {
return (
<View style={styles.container}>
<WebView
allowFileAccess
allowUniversalAccessFromFileURLs
allowingReadAccessToURL={path}
javaScriptEnabled
originWhitelist={['*']}
sharedCookiesEnabled
source={{
uri:
Platform.OS === 'ios'
? `file://${path}/index.html`
: `${path}/index.html`,
}}
style={styles.webview}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
webview: {
height: '100%',
width: '100%',
},
});
export default React.memo(Wrapper);

Deeplinking with a domain name

I have the following code in my App.js:
import React, { useState, useRef, useEffect } from 'react';
import { SafeAreaView, Text } from 'react-native';
import { NavigationContainer, useLinking } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
const Stack = createStackNavigator();
const Screen1 = () => <SafeAreaView><Text>Screen1</Text></SafeAreaView>;
const Screen2 = () => <SafeAreaView><Text>Screen2</Text></SafeAreaView>;
export default function App() {
const ref = useRef();
const [isReady, setIsReady] = useState(false);
const [initialState, setInitialState] = useState();
const { getInitialState } = useLinking(ref, {
prefixes: ['http://example.com', 'mychat://'],
config: {
screens: {
Screen2: 'screen-2',
},
},
});
useEffect(() => {
getInitialState().then((state) => {
if (state !== undefined) setInitialState(state);
setIsReady(true);
});
}, [getInitialState]);
if (!isReady) return null;
return (
<NavigationContainer ref={ref} initialState={initialState}>
<Stack.Navigator>
<Stack.Screen name='Screen1' component={Screen1} />
<Stack.Screen name='Screen2' component={Screen2} />
</Stack.Navigator>
</NavigationContainer>
);
}
Most of them are copied from https://reactnavigation.org/docs/deep-linking/ and https://reactnavigation.org/docs/use-linking/.
In the docs there is prefixes: ['https://mychat.com', 'mychat://'], I just changed https://mychat.com to http://example.com. But it doesn't seem to work.
When I open the following links in Safari:
mychat:// (works, gets redirected to app Screen1)
mychat://screen-2 (works, gets redirected to app Screen2)
http://example.com (just opens the link in the browser, no popup to redirect to app)
What change do I need to make to redirect the domain name to the mobile app? Am I missing something?
You need to use a domain that you have access to alongside a server.
Your server needs to host a couple of files, typically within the .well-known directory:
apple-app-site-association (note the .json is not needed)
assetlinks.json
You also need to enable some entitlements within your app for iOS, this may also be true for Android. On iOS, this will be enabling the Associated Domains entitlement alongside an entry for webcredentials:yourdomain.com
The documentation is pretty good to go through to give an understanding on what needs to be done in order to achieve Universal Links
https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html
https://developer.android.com/training/app-links/verify-site-associations
Examples:
iOS - https://stackoverflow.com/.well-known/apple-app-site-association
Android - https://stackoverflow.com/.well-known/assetlinks.json

Electron ES6 module import

Electron 3.0.0-beta.1
Node 10.2.0
Chromium 66.0.3359.181
The problem I'm having is importing a module. I created the following protocol:
protocol.registerFileProtocol('client', (request, callback) => {
var url = request.url.substr(8);
callback({path: path.join(__dirname, url)});
});
The output of the protocol is the correct path
"/Users/adviner/Projects/Client/src/ClientsApp/app.js"
I have the following module app.js with the following code:
export function square() {
return 'hello';
}
in my index.html I import the module like so:
<script type="module" >
import square from 'client://app.js';
console.log(square());
</script>
But I keep getting the error:
app.js/:1 Failed to load module script: The server responded with a non-JavaScript MIME type of "". Strict MIME type checking is enforced for module scripts per HTML spec.
I'm done searches but can't seem to find a solution. Can anyone suggest a way I can make this work?
Thanks
This is a tricky question and i will refer to Electron#12011 and this GitHub Gist for a deeper explaination but the core learning is that the corresponding HTML spec, disallows import via file:// (For XSS reasons) and a protocol must have the mime types defined.
The file protocol you use client:// has to set the correct mime-types when serving the files. Currently i would guess they are not set when you define the protocol via protocol.registerBufferProtocol thus you recive a The server responded with a non-JavaScript MIME type of "", the gist above has a code sample on how to do it.
Edit: I just want to emphasize the other answers here do only cover the absolute minimum basics implementation with no consideration of exceptions, security, or future changes. I highly recommend taking the time and read trough the gist I linked.
To confirm: this is there for security reasons.
However, in the event that you just need to get it deployed:
Change "target": "es2015" to "target": "es5" in your tsconfig.json file
Quick Solution:
const { protocol } = require( 'electron' )
const nfs = require( 'fs' )
const npjoin = require( 'path' ).join
const es6Path = npjoin( __dirname, 'www' )
// <= v4.x
// protocol.registerStandardSchemes( [ 'es6' ] )
// >= v5.x
protocol.registerSchemesAsPrivileged([
{ scheme: 'es6', privileges: { standard: true } }
])
app.on( 'ready', () => {
protocol.registerBufferProtocol( 'es6', ( req, cb ) => {
nfs.readFile(
npjoin( es6Path, req.url.replace( 'es6://', '' ) ),
(e, b) => { cb( { mimeType: 'text/javascript', data: b } ) }
)
})
})
<script type="module" src="es6://main.js"></script>
Based on flcoder solution for older Electron version.
Electron 5.0
const { protocol } = require('electron')
const nfs = require('fs')
const npjoin = require('path').join
const es6Path = npjoin(__dirname, 'www')
protocol.registerSchemesAsPrivileged([{ scheme: 'es6', privileges: { standard: true, secure: true } }])
app.on('ready', async () => {
protocol.registerBufferProtocol('es6', (req, cb) => {
nfs.readFile(
npjoin(es6Path, req.url.replace('es6://', '')),
(e, b) => { cb({ mimeType: 'text/javascript', data: b }) }
)
})
await createWindow()
})
Attention! The path always seems to be transformed to lowercase
<script type="module" src="es6://path/main.js"></script>
Sorry Viziionary, not enough reputation to answer the comment.
I've now done it like this:
https://gist.github.com/jogibear9988/3349784b875c7d487bf4f43e3e071612
my problem was, I also wanted to support modules which are imported via none relative path's, so I don't need to transpile my code.

React-native, how to get file-asset image absolute path?

I'm doing some image manipulation on ios on react-native.
The problem is one of the libraries I'm using only supports absolute paths, but I only have the file-asset uri.
Example
I have:
assets-library://asset/asset.HEIC?id=CE542E92-B1FF-42DC-BD89-D61BB70EB4BF&ext=HEIC
I need:
file:///Users/USERNAME/Library/Developer/CoreSimulator/Devices/########-####-####-####-############/data/Containers/Data/Application/########-####-####-####-############/Documents/########-####-####-####-############.jpg
Is there any way to easily get the image absolute path?
This is what I ended up doing, based on #ospfranco's answer.
I saved a copy of the asset on the temp folder. Also included a little snippet to generate a random string for the file name.
import RNFS from 'react-native-fs';
getAssetFileAbsolutePath = async (assetPath) => {
const dest = `${RNFS.TemporaryDirectoryPath}${Math.random().toString(36).substring(7)}.jpg`;
try {
let absolutePath = await RNFS.copyAssetsFileIOS(assetPath, dest, 0, 0);
console.log(absolutePath)
} catch(err) {
console.log(err)
}
}
So, the reason why you only get an url is because it image might not be stored on the device (it could be on iCloud). iOS silently downloads the asset for you once you do any operation on it.
That will not help you if you are really trying to manipulate the image from your react-native code though, so here is one workaround:
import RNFS from 'react-native-fs';
getAssetFileAbsolutePath = async (assetPath) => {
const dest = `${RNFS.TemporaryDirectoryPath}${Math.random().toString(36).substring(7)}.jpg`;
try {
let absolutePath = await RNFS.copyAssetsFileIOS(assetPath, dest, 0, 0);
} catch(err) {
// ...
}
}
Bare in mind this copies the file to a temporary directory which means it is not permanent, you can also copy it to your application's document directory.
I got it to work using RNFS, but I had to add a little 'extra' to the uri path to get it to work.
<TouchableHighlight
onPress={async () => {
const destPath = RNFS.CachesDirectoryPath + '/MyPic.jpg';
try {
await RNFS.copyAssetsFileIOS(imageUri, destPath, 0, 0);
console.log('destPath', destPath);
} catch (error) {
console.log(error);
}
navigation.navigate('SelectedPicture', {
uri: 'file://' + destPath,
});
}}>
<Image source={{uri: imageUri}} style={styles.image} />
</TouchableHighlight>
The question is old but i answer it to help people like me, new in react-native, having the same issue.
i were struggling with it trying to get the images from the cameraroll and process them with an OCR library. I was using react-native-photo-framework to get the images and i found that you can get fileurl using the method getImageMetadata with the assets. I need this fileurl because the original URI that has the format 'photo://...' wasn't being recognized as a valid URL for the OCR Library. I havenĀ“t tested it with real devices and with iCloud assets yet. Example:
const statusObj = await RNPhotosFramework.requestAuthorization()
if (statusObj.isAuthorized) {
const assetList = await RNPhotosFramework.getAssets({
includeMetadata: true,
includeResourcesMetadata: true,
fetchOptions: {
mediaTypes: ['image'],
sourceTypes: ['userLibrary', 'cloudShared', 'itunesSynced'],
}
})
const asset = photos.assets[0]
const metadata = await asset.getImageMetadata()
const uri = metadata.imageMetadata.fileUrl
}

How can i export and import from one file to another in Ract Native

var SummarizeAll=React.createClass({
render:function(){
var self=this;
var org = this.props.org;
var root=this.props.root;
var name=rootSchema;
var current=root;
return (<ScrollView><View style={{marginLeft:5,marginRight:5}}>
<Text>{current.displayName}</Text>
{
this.state.records.map(function(data,index){
return (<GenreicApp/>)
},this)
}
</View>
</View>
</View>
</View>
</View>
</ScrollView>)
}
})
this is in index.ios.js
var GenericSummary=React.createClass({
render:function(){
return (<View>
<GenericDisplayView/>
<SummarizeAll />
</View>)
}
}
})
this is in index2.ios.js
how can i export SummarizeAll component into GenericSumnmary Component in index2.ios.js file from index.ios.js file
You need to use Javascript (or rather Node.js) modules:
Place your SummarizeAll class in separate js file (summarize_all.js)
At the end of the js file add module.exports=SummarizeAll;
In the file that you want to use it use require directive: var SummarizeAll = require('./summarize_all');
Then you can similarly require the SummarizeAll component in index.ios.js or elsewhere. Having it in separate file/subdirectory makes it standalone and reusable.
For clarity and better modularisation you can also place the .js file in some subdirectory, in such case require should contain the relative path to your file including subdirectory.

Resources