Prebuilt UI in a mobile webview
This guide describes how to embed the Prebuilt UI in a native iOS or Android app using a webview and mobile-bootstrap.js.
Use it if you are building your own webview integration. Insurely also provides mobile SDKs for Android and iOS if you would rather not set the webview up yourself.
Before you start
You need the Customer ID and Config name from Insurely (see Introduction).
How it works
The webview loads the Prebuilt UI directly as the top-level page, using mobile-bootstrap.js.
Your app:
- Loads
{baseUrl}in a webview. - Injects a script that sets your configuration on
window.insurely, loads{baseUrl}/assets/mobile-bootstrap.js, and forwards the interface'spostMessageevents to native code. - Reacts to those events. If you need Swedish BankID, that includes
OPEN_SWEDISH_BANKID, which your app must handle by opening the BankID app.
{baseUrl} is the environment you were given:
| Environment | Base URL |
|---|---|
| Production | https://blocks.insurely.com |
| Test | https://blocks.test.insurely.com |
The examples below use the production URL.
Setting up the integration
The snippets below are minimal examples.
Set up the webview
Load the Prebuilt UI as the webview's top-level page.
let contentController = WKUserContentController()
contentController.addUserScript(WKUserScript(
source: bootstrapScript,
injectionTime: .atDocumentEnd,
forMainFrameOnly: false
))
contentController.addUserScript(WKUserScript(
source: postMessageMapperScript,
injectionTime: .atDocumentEnd,
forMainFrameOnly: false
))
contentController.add(insurelyMessageHandler, name: "iOSNative")
let configuration = WKWebViewConfiguration()
configuration.preferences.javaScriptCanOpenWindowsAutomatically = true
configuration.userContentController = contentController
let webView = WKWebView(frame: .zero, configuration: configuration)
webView.load(URLRequest(url: URL(string: "https://blocks.insurely.com")!))webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.settings.loadWithOverviewMode = true
webView.settings.useWideViewPort = true
webView.addJavascriptInterface(InsurelyMessageHandler(), "Android")
webView.webViewClient = InsurelyWebViewClient()
webView.loadUrl("https://blocks.insurely.com")Keep same-origin navigation inside the webview and hand everything else (external links and bankid:// URLs) to the OS:
inner class InsurelyWebViewClient : WebViewClient() {
override fun onPageCommitVisible(view: WebView?, url: String?) {
view?.loadUrl(BOOTSTRAP_JS)
super.onPageCommitVisible(view, url)
}
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
val requestedUrl = request?.url ?: return false
val currentUrl = view?.url?.let { Uri.parse(it) } ?: return false
val sameOrigin = requestedUrl.scheme == currentUrl.scheme &&
requestedUrl.host == currentUrl.host &&
requestedUrl.port == currentUrl.port
if (sameOrigin) {
view.loadUrl(requestedUrl.toString())
return true
}
context.startActivity(Intent(Intent.ACTION_VIEW, requestedUrl))
return true
}
}Inject your configuration and mobile-bootstrap.js
let bootstrapScript = """
(function() {
const bootstrapScript = document.createElement('script');
bootstrapScript.src = "https://blocks.insurely.com/assets/mobile-bootstrap.js";
document.head.appendChild(bootstrapScript);
window.insurely = {
config: {
customerId: 'YOUR_CUSTOMER_ID',
configName: 'YOUR_CONFIG_NAME',
},
};
})();
"""A second user script forwards every postMessage the interface sends to your native message handler:
let postMessageMapperScript = """
(function() {
window.originalPostMessage = window.postMessage;
window.postMessage = function(data, ...rest) {
window.originalPostMessage(data, ...rest);
window.webkit.messageHandlers.iOSNative.postMessage(data);
};
})();
"""BOOTSTRAP_JS (loaded in onPageCommitVisible above) sets your configuration, loads mobile-bootstrap.js, and forwards every postMessage the interface sends to the Android JavaScript interface:
private val BOOTSTRAP_JS = """
javascript:(function() {
const bootstrapScript = document.createElement('script');
bootstrapScript.src = 'https://blocks.insurely.com/assets/mobile-bootstrap.js';
document.head.appendChild(bootstrapScript);
window.insurely = {
config: {
customerId: 'YOUR_CUSTOMER_ID',
configName: 'YOUR_CONFIG_NAME',
},
};
if (window.Android) {
const originalPostMessage = window.postMessage;
window.postMessage = (data, ...rest) => {
originalPostMessage(data, ...rest);
Android.postMessage(JSON.stringify(data));
};
}
})()
""".trimIndent()All configuration and prefill options work exactly as in the web integration. Add prefill next to config if you want to skip the company selection step or pre-fill the user's details. See the Configuration reference for the full object.
Receive events natively
Messages have the shape { name: '<EVENT>', value: <payload> }. Handle the ones your integration needs and ignore the rest.
class InsurelyMessageHandler: NSObject, WKScriptMessageHandler {
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
guard
let body = message.body as? [String: Any],
let name = body["name"] as? String
else { return }
switch name {
case "OPEN_SWEDISH_BANKID":
guard
let value = body["value"] as? [String: Any],
let url = value["url"] as? String
else { return }
openBankID(url: url)
case "APP_CLOSE":
// Dismiss the webview
break
default:
// React to other events (COLLECTION_ID, COLLECTION_STATUS, RESULTS, ...) as needed
break
}
}
}inner class InsurelyMessageHandler {
@JavascriptInterface
fun postMessage(json: String?) {
val message = JSONObject(json ?: return)
when (message.optString("name")) {
"OPEN_SWEDISH_BANKID" -> {
val autostartToken = message.optJSONObject("value")?.optString("autostartToken")
if (autostartToken != null) openBankId(autostartToken)
}
"APP_CLOSE" -> { /* dismiss the webview */ }
// React to other events (COLLECTION_ID, COLLECTION_STATUS, RESULTS, ...) as needed
}
}
}Threading
Methods annotated with @JavascriptInterface are called on a background thread. Switch to the main thread before touching the WebView or any UI.
Open the BankID app (Sweden)
This step applies if you need Swedish BankID. When a user authenticates on the same device, the interface sends OPEN_SWEDISH_BANKID and your app must handle it, since the flow cannot continue without it. Skip this step if you do not.
The value contains a prebuilt url on the form bankid:///?autostarttoken=<token>&redirect=null, and the autostartToken separately if you prefer to build the URL yourself.
On iOS you must replace redirect=null with a URL scheme that reopens your app. This is what brings the user back automatically once they finish authenticating in the BankID app.
func openBankID(url urlString: String) {
guard var components = URLComponents(string: urlString) else { return }
// Replace the redirect parameter with your own app scheme.
// BankID requires the redirect value to be strictly percent-encoded,
// which is stricter than URLComponents' default encoding.
components.queryItems = (components.queryItems ?? []).filter { $0.name != "redirect" }
let redirect = "yourapp://".addingPercentEncoding(
withAllowedCharacters: .alphanumerics.union(CharacterSet(charactersIn: "."))
)!
guard
let base = components.url?.absoluteString,
let url = URL(string: "\(base)&redirect=\(redirect)")
else { return }
guard UIApplication.shared.canOpenURL(url) else {
// BankID is not installed. Show an error, or let the user
// authenticate with QR on another device
return
}
UIApplication.shared.open(url)
}Two things are required in your app for this to work:
- Register your URL scheme (
yourapp://) underCFBundleURLTypesinInfo.plist, so BankID can reopen your app. - Add
bankidtoLSApplicationQueriesSchemesinInfo.plistif you usecanOpenURLas above.
On Android, redirect=null is the correct value. When the user completes authentication, the BankID app closes and Android returns the user to your app on its own.
fun openBankId(autostartToken: String) {
val uri = Uri.parse("bankid:///")
.buildUpon()
.appendQueryParameter("autostarttoken", autostartToken)
.appendQueryParameter("redirect", "null")
.build()
try {
context.startActivity(Intent(Intent.ACTION_VIEW, uri))
} catch (e: ActivityNotFoundException) {
// BankID is not installed. Show an error, or let the user
// authenticate with QR on another device
}
}When BankID returns the user to your app, the webview resumes and the interface continues the flow. Nothing further is needed from your app.
Client-side authentication
The SWEDISH_MOBILE_BANKID_SAME_DEVICE_CLIENT_SIDE_AUTHENTICATION login method is different from the
other BankID login methods in that it requires the BankID flow to be initiated from the same device as
the BankID authentication will be performed on. In order to make this as streamlined as possible we
will in this case supply a number of requests to be executed from the end-user's device. These
requests reach your app on the COLLECTION_STATUS postMessage, as
Request objects in the INSTRUCTIONS field of its
extraInformation. The responses
are to be returned as a ResponseObject in a
SUPPLEMENTAL_INFORMATION postMessage back to the page.
This is visualised in the chart below. The two highlighted bands are the relay and the BankID switch, the phases your app has to take part in.
Every set of instructions carries an etag. A new etag means a new request to execute, so your app runs it and posts the response back. An etag it has already executed is a repeat of the same instructions and must be skipped, otherwise the request is sent to the company twice.
Returning the response
Your app injects the call into the page itself:
window.postMessage(
{
name: 'SUPPLEMENTAL_INFORMATION',
value: responseObject,
},
'*',
);value is a ResponseObject holding the headers
and body of the request your app just executed, with type set to RESPONSE_OBJECT. See the schema
for the full field reference and an example.
Read the full guide before you build this
This page only covers the webview side of the flow. The complete reference for this login method (the request and response objects, and what to do if your app cannot support arbitrary request URLs) is in the Swedish Wealth API introduction, or the Swedish Insurance API introduction for insurance.
Event reference
These are the events that matter most in a webview integration:
| Event | When | Action |
|---|---|---|
OPEN_SWEDISH_BANKID | Same-device Swedish BankID authentication | Required in Sweden: open the BankID app |
APP_CLOSE | The user presses the close button | Dismiss the webview |
PAGE_VIEW, SELECTED_COMPANY, COLLECTION_INITIATED, COLLECTION_ID, COLLECTION_STATUS, RESULTS | Flow progress | Informational. Use what your integration needs |
Scrolling-related events (SCROLL_TO_TOP, SCROLL_TO_POSITION, …) are handled by mobile-bootstrap.js itself, so you can ignore them.
For the full list of events and their payloads, see the Configuration reference. For the order they arrive in, see Standard flow.
Next steps
- Configuration reference - every setting, prefill option, and event
- Standard flow - the sequence of events a collection emits
- Collection methods - how data is collected per market and company
Last updated on