Key event handling
The events detailed below will need to be handled appropriately for the embedded components to fully integrate with your app.
Missing access token event
If an embedded component is loaded without a token being passed in, and guest user functionality isn't configured, a missing access token event will be sent back from the component. This can then be used to trigger a token fetch in your app.
Event format:
{
type:"MH_WIDGET_EVENT",
name: "MISSING_ACCESS_TOKEN"
}
Suggested handling
import android.content.Intent
import android.net.Uri
import android.util.Log
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.webkit.WebViewClient
import org.json.JSONObject
val webView: WebView = findViewById(R.id.webView)
webView.settings.javaScriptEnabled = true
val widgetUrl = "widget-url"
var token = null
val numCreditCards = 2
val hash = "your-hash" // this is received from the universal link
val widgetId = "your-widget-id"
// Set up a JavaScript interface to handle messages from the WebView
webView.addJavascriptInterface(object {
@JavascriptInterface
fun postMessage(message: String) {
handleMessageFromWebView(message)
}
}, "Android")
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
// Inject JavaScript to listen for messages
val jsCode = """
window.addEventListener('message', function(event) {
if (event.data) {
Android.postMessage(JSON.stringify(event.data));
}
});
"""
webView.evaluateJavascript(jsCode, null)
}
}
webView.loadUrl(widgetUrl)
fun handleMessageFromWebView(message: String) {
Log.d("DashboardFragment", "Received message from WebView: $message")
try {
val jsonObject = JSONObject(message)
when (val type = jsonObject.getString("name")) {
"MISSING_ACCESS_TOKEN" -> {
fetchToken("your-fetch-token-url")
}
else -> {
Log.d("DashboardFragment", "Received message $type")
}
}
} catch (e: Exception) {
Log.e("DashboardFragment", "Error parsing JSON message", e)
}
}
private fun fetchToken(url: String) {
try {
// fetch your token here
token = "new-fetched-token"
// Boot the widget with the new token
val jsCode = """
if (window.__boot && typeof window.__boot === 'function') {
window.__boot('$token', { numCreditCards: $numCreditCards, hash: '$hash', widget_id: '$widgetId' });
}
"""
webView.evaluateJavascript(jsCode, null)
} catch (e: Exception) {
Log.e("DashboardFragment", "Error fetching token", e)
}
}
<!DOCTYPE html>
<html>
<body>
<!-- Container for the widget -->
<div id="widget-container">
<iframe
id="widget-frame"
style="width: 100%; height: 100%; border: none"
></iframe>
</div>
<script type="text/javascript">
let token = null
function bootWidget() {
const frame = document.getElementById("widget-frame")
frame.src = window.location.origin
// Wait for iframe to load then send boot message
frame.onload = function () {
const config = {
action: "boot",
token: token,
params: {
widget_id: widgetId,
hash,
...options,
},
}
// Add a small delay to ensure widget is ready
setTimeout(() => {
frame.contentWindow.postMessage(config, "*")
}, 100)
}
}
const getToken = async () => {
// api call to get your token here
return "your-token"
}
// Listen for messages from the widget
window.addEventListener("message", async function (event) {
if (
event.data?.source !== "react-devtools-content-script" &&
event.data?.type === "MH_WIDGET_EVENT" &&
event.data?.name !== "click"
) {
console.log("Widget Event:", event.data)
}
if (
event.data?.name === "MISSING_ACCESS_TOKEN"
) {
const fetchedToken = await getToken()
if (fetchedToken) {
token = fetchedToken
bootWidget()
}
}
})
document.getElementById("updateButton").onclick = bootWidget
</script>
</body>
</html>
import UIKit
import WebKit
// Define a struct for the payload
struct Payload: Codable {
let name: String
let url: String?
}
class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {
var webView: WKWebView!
var token = "your-token" // Initial token, can be updated after fetching
override func viewDidLoad() {
super.viewDidLoad()
// Set up the web view configuration
let preferences = WKPreferences()
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
configuration.userContentController.add(self, name: "ios")
// Initialize the web view
webView = WKWebView(frame: self.view.frame, configuration: configuration)
webView.navigationDelegate = self
self.view.addSubview(webView)
// Define the widget URL and parameters
let widgetUrl = "https://widget-url"
let numCreditCards = 2 // Example of additional config
let hash = "your-hash" // Received from the universal link
let widgetId = "your-widget-id"
// Load the URL
if let url = URL(string: widgetUrl) {
webView.load(URLRequest(url: url))
}
}
// WKNavigationDelegate method
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let jsCode = """
if (window.__boot && typeof window.__boot === 'function') {
window.__boot('\(token)', { numCreditCards: \(numCreditCards), hash: '\(hash)', widget_id: '\(widgetId)' });
}
window.addEventListener('message', function(event) {
if (event.data) {
window.webkit.messageHandlers.ios.postMessage(JSON.stringify(event.data));
}
});
"""
webView.evaluateJavaScript(jsCode, completionHandler: nil)
}
// WKScriptMessageHandler method
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "ios", let jsonString = message.body as? String {
print("Received message from JS: \(jsonString)")
guard let jsonData = jsonString.data(using: .utf8) else {
fatalError("Failed to convert string to data")
}
do {
let decoder = JSONDecoder()
let payload = try decoder.decode(Payload.self, from: jsonData)
if payload.name == "MISSING_ACCESS_TOKEN" {
fetchToken()
}
} catch {
print("Error decoding JSON:", error)
}
}
}
private func fetchToken() {
// Fetch token here
token = "your-token"
// Boot the widget with the new token
let jsCode = """
if (window.__boot && typeof window.__boot === 'function') {
window.__boot('\(token)', { numCreditCards: \(numCreditCards), hash: '\(hash)', widget_id: '\(widgetId)' });
}
"""
webView.evaluateJavaScript(jsCode, completionHandler: nil)
}
}
import React, { useRef, useState } from 'react';
import { View, Alert } from 'react-native';
import { WebView } from 'react-native-webview';
const App = () => {
const webViewRef = useRef(null);
const [token, setToken] = useState('your-token'); // Initial token
const widgetUrl = 'https://widget-url';
const numCreditCards = 2;
const hash = 'your-hash';
const widgetId = 'your-widget-id';
const handleWebViewMessage = (event) => {
try {
const message = JSON.parse(event.nativeEvent.data);
if (message.name === 'MISSING_ACCESS_TOKEN') {
fetchToken();
}
} catch (error) {
console.error('Error parsing message from WebView:', error);
}
};
const fetchToken = () => {
// Simulate fetching a new token
const newToken = 'new-fetched-token';
setToken(newToken);
// Boot the widget with the new token
const jsCode = `
if (window.__boot && typeof window.__boot === 'function') {
window.__boot('${newToken}', { numCreditCards: ${numCreditCards}, hash: '${hash}', widget_id: '${widgetId}' });
}
`;
webViewRef.current.injectJavaScript(jsCode);
};
const injectedJavaScript = `
(function() {
if (window.__boot && typeof window.__boot === 'function') {
window.__boot('${token}', { numCreditCards: ${numCreditCards}, hash: '${hash}', widget_id: '${widgetId}' });
}
window.addEventListener('message', function(event) {
if (event.data) {
window.ReactNativeWebView.postMessage(JSON.stringify(event.data));
}
});
})();
`;
return (
<View style={{ flex: 1 }}>
<WebView
ref={webViewRef}
source={{ uri: widgetUrl }}
onMessage={handleWebViewMessage}
injectedJavaScript={injectedJavaScript}
javaScriptEnabled={true}
/>
</View>
);
};
export default App;
// main.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:url_launcher/url_launcher.dart';
void main() {
runApp(
MaterialApp(theme: ThemeData(useMaterial3: true), home: const WebViewApp()),
);
}
class WebViewApp extends StatefulWidget {
const WebViewApp({super.key});
@override
State<WebViewApp> createState() => _WebViewAppState();
}
class _WebViewAppState extends State<WebViewApp> {
late final WebViewController controller;
bool _hasBooted = false;
Future<String?> fetchToken() async {
try {
final response = await http.get(
Uri.parse('your_token_url'),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return data['access_token'];
} else {
debugPrint('Failed to fetch token: ${response.statusCode}');
}
} catch (e) {
debugPrint('Error fetching token: $e');
}
return null;
}
@override
void initState() {
super.initState();
controller =
WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..loadRequest(
Uri.parse('your_widget_url'),
)
..addJavaScriptChannel(
'Android',
onMessageReceived: handleMessageFromWebView,
)
..addJavaScriptChannel(
'webkit.messageHandlers.iosListener',
onMessageReceived: handleMessageFromWebView,
)
..setNavigationDelegate(
NavigationDelegate(
onPageFinished: (String url) async {
if (!_hasBooted) {
final token = await fetchToken();
if (token != null) {
await controller.runJavaScript(
'window.__boot("$token", {widget_id: "your_widget_id"})',
);
_hasBooted = true;
}
}
},
),
);
}
void handleMessageFromWebView(JavaScriptMessage message) async {
debugPrint('\x1B[33m[WebView] ${message.message}\x1B[0m'); // Yellow color
try {
final jsonObject = jsonDecode(message.message);
final type = jsonObject['type'] as String;
switch (type) {
case 'MISSING_ACCESS_TOKEN':
final newToken = await fetchToken();
if (newToken != null) {
await controller.runJavaScript(
'window.__boot("$newToken", {widget_id: "your_widget_id"})',
);
}
break;
default:
debugPrint('\x1B[36m[WebView Event] $type\x1B[0m'); // Cyan color
}
} catch (e) {
debugPrint('\x1B[31m[WebView Error] $e\x1B[0m'); // Red color
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter WebView')),
body: WebViewWidget(controller: controller),
);
}
}
External web link event
When the embedded component journey requires a link out to an external site, we emit an event with type = "externalLink". Handling this appropriately will be required for any open banking flows, and also will be needed to link out to other sites or areas of your own app.
Event format:
{
url: "https://www.google.com",
type: "externalLink"
}
Suggested handling
You will need to use native platform apis to open the url passed in via the event.
import android.content.Intent
import android.net.Uri
import android.util.Log
import org.json.JSONObject
fun handleMessageFromWebView(message: String) {
Log.d("DashboardFragment", "Received message from WebView: $message")
try {
val jsonObject = JSONObject(message)
when (val type = jsonObject.getString("type")) {
"externalLink" -> {
val url = jsonObject.getString("url")
openExternalLink(url)
}
else -> {
Log.d("DashboardFragment", "Received message $type")
}
}
} catch (e: Exception) {
Log.e("DashboardFragment", "Error parsing JSON message", e)
}
}
private fun openExternalLink(url: String) {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
} catch (e: Exception) {
Log.e("DashboardFragment", "Error opening external link", e)
}
}
const handleExternalLink = async (event) => {
// Handle external link event
if (event.data?.type === 'externalLink' && event.data?.url) {
window.open(event.data.url, '_blank');
}
};
import UIKit
import WebKit
struct Payload: Codable {
let type: String
let url: String
}
// WKScriptMessageHandler method
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "ios", let jsonString = message.body as? String {
print("Received message from JS: \(jsonString)")
guard let jsonData = jsonString.data(using: .utf8) else {
fatalError("Failed to convert string to data")
}
do {
let decoder = JSONDecoder()
let payload = try decoder.decode(Payload.self, from: jsonData)
if payload.type == "externalLink", let url = URL(string: payload.url) {
print("OPEN URL: \(payload.url)")
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
print("Cannot open URL")
}
}
} catch {
print("Error decoding JSON:", error)
}
}
}
import React, { useRef } from 'react';
import { Linking, Alert } from 'react-native';
const handleMessage = (event) => {
try {
const message = JSON.parse(event.nativeEvent.data);
console.log('Received message from WebView:', message);
if (message.type === 'externalLink') {
const url = message.url;
openExternalLink(url);
} else {
console.log('Received message type:', message.type);
}
} catch (error) {
console.error('Error parsing JSON message:', error);
}
};
const openExternalLink = (url) => {
Linking.canOpenURL(url)
.then((supported) => {
if (supported) {
Linking.openURL(url);
} else {
console.error('Cannot open URL:', url);
Alert.alert('Error', 'Cannot open the link.');
}
})
.catch((err) => console.error('An error occurred', err));
};
// main.dart
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:url_launcher/url_launcher.dart';
void handleMessageFromWebView(JavaScriptMessage message) {
debugPrint('\x1B[33m[WebView] ${message.message}\x1B[0m'); // Yellow color
try {
final jsonObject = jsonDecode(message.message);
final type = jsonObject['type'] as String;
switch (type) {
case 'externalLink':
final url = jsonObject['url'] as String;
openExternalLink(url);
default:
debugPrint('\x1B[36m[WebView Event] $type\x1B[0m'); // Cyan color
}
} catch (e) {
debugPrint('\x1B[31m[WebView Error] $e\x1B[0m'); // Red color
}
}
void openExternalLink(String url) async {
final uri = Uri.parse(url);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
} else {
debugPrint('Could not launch $url');
}
}
Close event
This is fired when a CTA to leave the embedded component is clicked. This will automatically be sent when the user clicks the "Exit" button (shown on the initial/home page of the component) but can be sent for other CTAs as well.
Event format:
{
metadata: {route: 'indexRoute', baseURI: {YOUR_DOMAIN}, hash: ''},
name:"close",
type:"MH_WIDGET_EVENT"
}
Suggested handling
You will need to use native platform apis to link back to the desired area of your app.
import android.content.Intent
import android.net.Uri
import android.util.Log
import org.json.JSONObject
fun handleMessageFromWebView(message: String) {
Log.d("DashboardFragment", "Received message from WebView: $message")
try {
val jsonObject = JSONObject(message)
when (val type = jsonObject.getString("name")) {
"close" -> {
openExternalLink(YOUR_APP_URL)
}
else -> {
Log.d("DashboardFragment", "Received message $type")
}
}
} catch (e: Exception) {
Log.e("DashboardFragment", "Error parsing JSON message", e)
}
}
private fun openExternalLink(url: String) {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
} catch (e: Exception) {
Log.e("DashboardFragment", "Error opening external link", e)
}
}
const handleCloseEvent = async (event) => {
if (event.data?.name === 'close') {
window.open({YOUR_APP_URL}, '_blank');
}
};
import UIKit
import WebKit
struct Payload: Codable {
let type: String
let url: String
}
// WKScriptMessageHandler method
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "ios", let jsonString = message.body as? String {
print("Received message from JS: \(jsonString)")
guard let jsonData = jsonString.data(using: .utf8) else {
fatalError("Failed to convert string to data")
}
do {
let decoder = JSONDecoder()
let payload = try decoder.decode(Payload.self, from: jsonData)
if payload.name == "close", let url = URL(YOUR_APP_URL) {
print("OPEN URL: \(payload.url)")
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
print("Cannot open URL")
}
}
} catch {
print("Error decoding JSON:", error)
}
}
}
import React, { useRef } from 'react';
import { Linking, Alert } from 'react-native';
const handleMessage = (event) => {
try {
const message = JSON.parse(event.nativeEvent.data);
console.log('Received message from WebView:', message);
if (message.name === 'close') {
openExternalLink(YOUR_APP_URL);
} else {
console.log('Received message type:', message.type);
}
} catch (error) {
console.error('Error parsing JSON message:', error);
}
};
const openExternalLink = (url) => {
Linking.canOpenURL(url)
.then((supported) => {
if (supported) {
Linking.openURL(url);
} else {
console.error('Cannot open URL:', url);
Alert.alert('Error', 'Cannot open the link.');
}
})
.catch((err) => console.error('An error occurred', err));
};
// main.dart
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:url_launcher/url_launcher.dart';
void handleMessageFromWebView(JavaScriptMessage message) {
debugPrint('\x1B[33m[WebView] ${message.message}\x1B[0m'); // Yellow color
try {
final jsonObject = jsonDecode(message.message);
final type = jsonObject['type'] as String;
switch (name) {
case 'close':
openExternalLink(YOUR_APP_URL);
default:
debugPrint('\x1B[36m[WebView Event] $type\x1B[0m'); // Cyan color
}
} catch (e) {
debugPrint('\x1B[31m[WebView Error] $e\x1B[0m'); // Red color
}
}
void openExternalLink(String url) async {
final uri = Uri.parse(url);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
} else {
debugPrint('Could not launch $url');
}
}
Updated 12 days ago