Income Navigator Webview Integration
Nova Credit supports integrating Income Navigator® into native web applications through the use of a WebView in both iOS and Android.
Minimum iOS version supported: 15.0
Minimum Android version supported: 12.0
Settings
- Javascript enabled must be set to
true javaScriptCanOpenWindowsAutomaticallymust be set totruesetSupportMultipleWindowsmust be set totrue(Android)
React Native
import { WebView } from 'react-native-webview';
<WebView javaScriptEnabled javaScriptCanOpenWindowsAutomatically setSupportMultipleWindows />;
Swift
let preferences = WKWebpagePreferences()
preferences.allowsContentJavaScript = true
webView.configuration.defaultWebpagePreferences = preferences
webView.configuration.preferences.javaScriptCanOpenWindowsAutomatically = true
Kotlin
val webView = WebView(context)
webView.settings.javaScriptEnabled = true
webView.settings.javaScriptCanOpenWindowsAutomatically = true
webView.settings.setSupportMultipleWindows(true)
Navigation Handling
External links need to open in an external browser i.e. bank OAuth flows. This requires some explicit handling depending on the platform.
React Native
import { Linking } from 'react-native';
import { WebView } from 'react-native-webview';
<WebView
onOpenWindow={({ nativeEvent }) => {
Linking.openURL(nativeEvent.targetUrl);
}}
/>;
Swift
// Set the ui delegate
webView.uiDelegate = self
// This method is called when a new window/tab is requested (e.g., target="_blank")
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.request.url != nil {
if UIApplication.shared.canOpenURL(navigationAction.request.url!) {
UIApplication.shared.open(navigationAction.request.url!, options: [:], completionHandler: nil)
}
}
return nil
}
Kotlin
// This method is called when a new window/tab is requested (e.g., target="_blank")
webView.webChromeClient = object : WebChromeClient() {
override fun onCreateWindow(
view: WebView?,
isDialog: Boolean,
isUserGesture: Boolean,
resultMsg: android.os.Message?
): Boolean {
// Create a temporary, invisible WebView to intercept the URL
val tempWebView = WebView(this@MainActivity)
tempWebView.settings.javaScriptEnabled = true // Essential for some redirects
tempWebView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(tempView: WebView?, request: WebResourceRequest?): Boolean {
val url = request?.url.toString()
if (url.isNotEmpty()) {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
} catch (e: Exception) {
e.printStackTrace()
}
}
return true
}
}
// Attach the temporary WebView to the message so it receives the new window's load
val transport = resultMsg?.obj as WebView.WebViewTransport
transport.webView = tempWebView
resultMsg.sendToTarget()
return true
}
}
Permissions
In iOS the paystub upload require Camera, Photo Library, and File System usage so the appropriate permissions must be added to the Info.plist. For Android if you use the file picker then no explicit file permissions are required.
iOS: Info.plist
- Privacy - Camera Usage Description
- Privacy - Photo Library Usage Description
Android: AndroidManifest.xml
- android.permission.INTERNET
React Native Expo: app.json
{
"android": {
"permissions": ["android.permission.INTERNET"]
},
"ios": {
"infoPlist": {
"NSCameraUsageDescription": "Camera Usage Description",
"NSPhotoLibraryUsageDescription": "Photo Library Usage Description"
}
}
}
Additionally, handling these permissions inside of a Webview for iOS requires some additional logic in code to check and grant permissions.
Swift
func webView(_ webView: WKWebView, decideMediaCapturePermissionsFor origin: WKSecurityOrigin,
initiatedBy frame: WKFrameInfo, type: WKMediaCaptureType) async -> WKPermissionDecision {
if type == .camera, await checkCameraPermissions(){
return .grant
}
else{
return .prompt
}
}
func checkCameraPermissions() async -> Bool{
let authStatus = AVCaptureDevice.authorizationStatus(for: .video)
if (authStatus != .authorized){
return await AVCaptureDevice.requestAccess(for: .video)
}
return authStatus == .authorized;
}
Kotlin
Android requires some additional logic for file picker handling if you are using paystub uploads.
class MainActivity : AppCompatActivity() {
private var mFilePathCallback: ValueCallback<Array<Uri>>? = null // To send file URIs back to WebView
private val FILE_CHOOSER_REQUEST_CODE = 123 // Request code for startActivityForResult
private val PERMISSION_REQUEST_CODE = 456 // Request code for ActivityCompat.requestPermissions
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val webView: WebView = findViewById(R.id.webView)
webView.settings.javaScriptEnabled = true
webView.settings.javaScriptCanOpenWindowsAutomatically = true
webView.settings.setSupportMultipleWindows(true)
webView.webChromeClient = object : WebChromeClient() {
// This function is called when file picker is opened
override fun onShowFileChooser(
webView: WebView?,
filePathCallback: ValueCallback<Array<Uri>>?,
fileChooserParams: FileChooserParams?
): Boolean {
mFilePathCallback = filePathCallback
openFileChooser(fileChooserParams)
return true
}
}
webView.loadUrl("webviewUrl")
}
// Helper function to launch the system file picker
private fun openFileChooser(fileChooserParams: WebChromeClient.FileChooserParams?) {
val mimeTypes = arrayOf("image/png", "image/jpeg", "application/pdf")
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
setType(mimeTypes.joinToString(separator= "|"))
putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
}
try {
startActivityForResult(intent, FILE_CHOOSER_REQUEST_CODE)
} catch (e: Exception) {
e.printStackTrace()
// Cancel the WebView request if picker fails to open
mFilePathCallback?.onReceiveValue(null)
mFilePathCallback = null
}
}
// Handle the result from the file picker Intent
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == FILE_CHOOSER_REQUEST_CODE) {
if (mFilePathCallback == null) {
return
}
// Get selected Uri(s)
val result = if (resultCode == Activity.RESULT_OK) {
data?.dataString?.let { arrayOf(Uri.parse(it)) } ?: data?.clipData?.let { clipData ->
Array(clipData.itemCount) { i -> clipData.getItemAt(i).uri }
}
} else {
// User cancelled the picker
null
}
// Send the result back to the WebView
mFilePathCallback?.onReceiveValue(result)
mFilePathCallback = null // Clear the callback
}
}
}
Callback handling
You can handle client side callbacks onSuccess, onError, and onExit by posting a message to the WebView. More details about client side hooks are available in our Quickstart Guide.
JS registration
// When registering Nova Credit handle the callbacks
window.Nova.register({
env: 'sandbox',
productId: 'productId',
publicId: 'publicId',
onSuccess: function(publicToken, status){
const msg = JSON.stringify({ message:'NOVA_SUCCESS', publicToken, status });
// React Native
window.ReactNativeWebView.postMessage(msg, '*');
// iOS
window.webkit.messageHandlers.native.postMessage(msg, '*');
// Android
window.parent.postMessage(msg, '*');
}
onError: function(publicToken, error){
const msg = JSON.stringify({ message:'NOVA_ERROR', publicToken, error });
// postMessage(msg, '*');
}
onExit: function(){
const msg = JSON.stringify({ message:'NOVA_EXIT'});
// postMessage(msg, '*');
},
});
React Native
<Webview onMessage={event => console.log(event.nativeEvent.data)} />
Swift
let configuration = WKWebViewConfiguration()
configuration.userContentController.add(self, name: "NOVA_MESSAGE")
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "NOVA_MESSAGE" {
// Parse message
}
}
Kotlin
val novaListener = object : WebMessageListener() {
override fun onPostMessage(
view: WebView,
message: WebMessageCompat,
sourceOrigin: Uri,
isMainFrame: Boolean,
replyProxy: JavaScriptReplyProxy
) {
// Parse message
}
}
WebViewCompat.addWebMessageListener(webView, "myObject", rules, novaListener)