diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml
deleted file mode 100644
index 08cb7b47..00000000
--- a/.idea/libraries/Dart_Packages.xml
+++ /dev/null
@@ -1,738 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml
index 31799730..65bb3679 100755
--- a/.idea/libraries/Flutter_Plugins.xml
+++ b/.idea/libraries/Flutter_Plugins.xml
@@ -1,6 +1,8 @@
-
+
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 667dce09..2d1d2144 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,12 @@
+## 5.2.0
+
+- Added `WebMessageChannel` and `WebMessageListener` features
+- `AndroidInAppWebViewController.getCurrentWebViewPackage` is available now starting from Android API 21+.
+- Updated Android Gradle distributionUrl version to `5.6.4`
+- Attempt to fix "InAppBrowserActivity.onCreate NullPointerException - Attempt to invoke virtual method 'java.lang.String android.os.Bundle.getString(java.lang.String)' on a null object reference" [#665](https://github.com/pichillilorenzo/flutter_inappwebview/issues/665)
+- Fixed "[iOS] Application crashes when processing onCreateWindow" [#579](https://github.com/pichillilorenzo/flutter_inappwebview/issues/579)
+- Fixed wrong mapping of `NavigationAction` class on Android for `androidHasGesture` and `androidIsRedirect` properties
+
## 5.1.0+4
- Fixed "IOS scrolling crash the application" [#707](https://github.com/pichillilorenzo/flutter_inappwebview/issues/707)
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index 01a286e9..fd50c3ca 100755
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
\ No newline at end of file
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index aa9e7a21..40dc1267 100755
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -2,8 +2,13 @@
-
-
+
+
= Build.VERSION_CODES.M &&
+ WebViewFeature.isFeatureSupported(WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL)) {
+ result.success(webView.createCompatWebMessageChannel().toMap());
+ } else {
+ result.success(null);
+ }
+ break;
+ case "postWebMessage":
+ if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
+ WebViewFeature.isFeatureSupported(WebViewFeature.POST_WEB_MESSAGE)) {
+ Map message = (Map) call.argument("message");
+ String targetOrigin = (String) call.argument("targetOrigin");
+ List ports = new ArrayList<>();
+ List
\ No newline at end of file
diff --git a/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift b/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift
index f3026c01..21589f01 100755
--- a/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift
+++ b/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift
@@ -75,6 +75,7 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega
pullToRefreshControl.prepare()
prepareWebView()
+ webView.windowCreated = true
progressBar = UIProgressView(progressViewStyle: .bar)
diff --git a/ios/Classes/InAppWebView/FlutterWebViewController.swift b/ios/Classes/InAppWebView/FlutterWebViewController.swift
index b927e5b1..0ebe03e9 100755
--- a/ios/Classes/InAppWebView/FlutterWebViewController.swift
+++ b/ios/Classes/InAppWebView/FlutterWebViewController.swift
@@ -82,6 +82,7 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView {
webView!.options = options
webView!.prepare()
+ webView!.windowCreated = true
if windowId == nil {
if #available(iOS 11.0, *) {
diff --git a/ios/Classes/InAppWebView/InAppWebView.swift b/ios/Classes/InAppWebView/InAppWebView.swift
index 683994ac..e51db2b2 100755
--- a/ios/Classes/InAppWebView/InAppWebView.swift
+++ b/ios/Classes/InAppWebView/InAppWebView.swift
@@ -12,10 +12,13 @@ import WebKit
public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIGestureRecognizerDelegate, PullToRefreshDelegate {
var windowId: Int64?
+ var windowCreated = false
var inAppBrowserDelegate: InAppBrowserDelegate?
var channel: FlutterMethodChannel?
var options: InAppWebViewOptions?
var pullToRefreshControl: PullToRefreshControl?
+ var webMessageChannels: [String:WebMessageChannel] = [:]
+ var webMessageListeners: [WebMessageListener] = []
static var sslCertificatesMap: [String: SslCertificate] = [:] // [URL host name : SslCertificate]
static var credentialsProposed: [URLCredential] = []
@@ -450,6 +453,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
configuration.userContentController.removeScriptMessageHandler(forName: "onCallAsyncJavaScriptResultBelowIOS14Received")
configuration.userContentController.add(self, name: "onCallAsyncJavaScriptResultBelowIOS14Received")
+ configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessagePortMessageReceived")
+ configuration.userContentController.add(self, name: "onWebMessagePortMessageReceived")
+ configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessageListenerPostMessageReceived")
+ configuration.userContentController.add(self, name: "onWebMessageListenerPostMessageReceived")
configuration.userContentController.addUserOnlyScripts(initialUserScripts)
configuration.userContentController.sync(scriptMessageHandler: self)
}
@@ -1418,6 +1425,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
+ if windowId != nil, !windowCreated {
+ decisionHandler(.cancel)
+ return
+ }
+
if navigationAction.request.url != nil {
if let useShouldOverrideUrlLoading = options?.useShouldOverrideUrlLoading, useShouldOverrideUrlLoading {
@@ -1513,6 +1525,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
+ disposeWebMessageChannels()
initializeWindowIdJS()
if #available(iOS 14.0, *) {
@@ -1561,6 +1574,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
+ if windowId != nil, !windowCreated {
+ completionHandler(.cancelAuthenticationChallenge, nil)
+ return
+ }
+
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic ||
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodDefault ||
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest ||
@@ -1573,6 +1591,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
onReceivedHttpAuthRequest(challenge: challenge, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message ?? "")
+ completionHandler(.performDefaultHandling, nil)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
completionHandler(.performDefaultHandling, nil)
@@ -1642,6 +1661,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
onReceivedServerTrustAuthRequest(challenge: challenge, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message ?? "")
+ completionHandler(.performDefaultHandling, nil)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
completionHandler(.performDefaultHandling, nil)
@@ -1677,6 +1697,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
onReceivedClientCertRequest(challenge: challenge, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message ?? "")
+ completionHandler(.performDefaultHandling, nil)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
completionHandler(.performDefaultHandling, nil)
@@ -1801,6 +1822,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
onJsAlert(frame: frame, message: message, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message ?? "")
+ completionHandler()
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
self.createAlertDialog(message: message, responseMessage: nil, confirmButtonTitle: nil, completionHandler: completionHandler)
@@ -1862,6 +1884,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
onJsConfirm(frame: frame, message: message, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message ?? "")
+ completionHandler(false)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
self.createConfirmDialog(message: message, responseMessage: nil, confirmButtonTitle: nil, cancelButtonTitle: nil, completionHandler: completionHandler)
@@ -1937,6 +1960,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
onJsPrompt(frame: frame, message: message, defaultValue: defaultValue, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message ?? "")
+ completionHandler(nil)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
self.createPromptDialog(message: message, defaultValue: defaultValue, responseMessage: nil, confirmButtonTitle: nil, cancelButtonTitle: nil, value: nil, completionHandler: completionHandler)
@@ -2068,9 +2092,15 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
public func webView(_ webView: WKWebView,
authenticationChallenge challenge: URLAuthenticationChallenge,
shouldAllowDeprecatedTLS decisionHandler: @escaping (Bool) -> Void) {
+ if windowId != nil, !windowCreated {
+ decisionHandler(false)
+ return
+ }
+
shouldAllowDeprecatedTLS(challenge: challenge, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message ?? "")
+ decisionHandler(false)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
decisionHandler(false)
@@ -2511,6 +2541,45 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
])
callAsyncJavaScriptBelowIOS14Results.removeValue(forKey: resultUuid)
}
+ } else if message.name == "onWebMessagePortMessageReceived" {
+ let body = message.body as! [String: Any?]
+ let webMessageChannelId = body["webMessageChannelId"] as! String
+ let index = body["index"] as! Int64
+ let webMessage = body["message"] as? String
+ if let webMessageChannel = webMessageChannels[webMessageChannelId] {
+ webMessageChannel.onMessage(index: index, message: webMessage)
+ }
+ } else if message.name == "onWebMessageListenerPostMessageReceived" {
+ let body = message.body as! [String: Any?]
+ let jsObjectName = body["jsObjectName"] as! String
+ let messageData = body["message"] as? String
+ if let webMessageListener = webMessageListeners.first(where: ({($0.jsObjectName == jsObjectName)})) {
+ let isMainFrame = message.frameInfo.isMainFrame
+
+ var scheme: String? = nil
+ var host: String? = nil
+ var port: Int? = nil
+ if #available(iOS 9.0, *) {
+ let sourceOrigin = message.frameInfo.securityOrigin
+ scheme = sourceOrigin.protocol
+ host = sourceOrigin.host
+ port = sourceOrigin.port
+ } else if let url = message.frameInfo.request.url {
+ scheme = url.scheme
+ host = url.host
+ port = url.port
+ }
+
+ if !webMessageListener.isOriginAllowed(scheme: scheme, host: host, port: port) {
+ return
+ }
+
+ var sourceOrigin: URL? = nil
+ if let scheme = scheme, !scheme.isEmpty, let host = host, !host.isEmpty {
+ sourceOrigin = URL(string: "\(scheme)://\(host)\(port != nil && port != 0 ? ":" + String(port!) : "")")
+ }
+ webMessageListener.onPostMessage(message: messageData, sourceOrigin: sourceOrigin, isMainFrame: isMainFrame)
+ }
}
}
@@ -2685,15 +2754,74 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
}
}
+ public func createWebMessageChannel(completionHandler: ((WebMessageChannel) -> Void)? = nil) -> WebMessageChannel {
+ let id = NSUUID().uuidString
+ let webMessageChannel = WebMessageChannel(id: id)
+ webMessageChannel.initJsInstance(webView: self, completionHandler: completionHandler)
+ webMessageChannels[id] = webMessageChannel
+
+ return webMessageChannel
+ }
+
+ public func postWebMessage(message: WebMessage, targetOrigin: String, completionHandler: ((Any?) -> Void)? = nil) throws {
+ var portsString = "null"
+ if let ports = message.ports {
+ var portArrayString: [String] = []
+ for port in ports {
+ if port.isStarted {
+ throw NSError(domain: "Port is already started", code: 0)
+ }
+ if port.isClosed || port.isTransferred {
+ throw NSError(domain: "Port is already closed or transferred", code: 0)
+ }
+ port.isTransferred = true
+ portArrayString.append("\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)['\(port.webMessageChannel!.id)'].\(port.name)")
+ }
+ portsString = "[" + portArrayString.joined(separator: ", ") + "]"
+ }
+ let data = message.data?.replacingOccurrences(of: "\'", with: "\\'") ?? "null"
+ let url = URL(string: targetOrigin)?.absoluteString ?? "*"
+ let source = """
+ (function() {
+ window.postMessage('\(data)', '\(url)', \(portsString));
+ })();
+ """
+ evaluateJavascript(source: source, completionHandler: completionHandler)
+ message.dispose()
+ }
+
+ public func addWebMessageListener(webMessageListener: WebMessageListener) throws {
+ if webMessageListeners.map({ ($0.jsObjectName) }).contains(webMessageListener.jsObjectName) {
+ throw NSError(domain: "jsObjectName \(webMessageListener.jsObjectName) was already added.", code: 0)
+ }
+ try webMessageListener.assertOriginRulesValid()
+ webMessageListener.initJsInstance(webView: self)
+ webMessageListeners.append(webMessageListener)
+ }
+
+ public func disposeWebMessageChannels() {
+ for webMessageChannel in webMessageChannels.values {
+ webMessageChannel.dispose()
+ }
+ webMessageChannels.removeAll()
+ }
+
public func dispose() {
if isPausedTimers, let completionHandler = isPausedTimersCompletionHandler {
isPausedTimersCompletionHandler = nil
completionHandler()
}
stopLoading()
+ disposeWebMessageChannels()
+ for webMessageListener in webMessageListeners {
+ webMessageListener.dispose()
+ }
+ webMessageListeners.removeAll()
if windowId == nil {
configuration.userContentController.removeAllPluginScriptMessageHandlers()
configuration.userContentController.removeScriptMessageHandler(forName: "onCallAsyncJavaScriptResultBelowIOS14Received")
+ configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessagePortMessageReceived")
+ configuration.userContentController.removeScriptMessageHandler(forName: "onWebMessageListenerPostMessageReceived")
configuration.userContentController.removeAllUserScripts()
if #available(iOS 11.0, *) {
configuration.userContentController.removeAllContentRuleLists()
diff --git a/ios/Classes/InAppWebViewMethodHandler.swift b/ios/Classes/InAppWebViewMethodHandler.swift
index f8437fd6..5d416b3a 100644
--- a/ios/Classes/InAppWebViewMethodHandler.swift
+++ b/ios/Classes/InAppWebViewMethodHandler.swift
@@ -474,6 +474,57 @@ public class InAppWebViewMethodHandler: FlutterMethodCallDelegate {
result(false)
}
break
+ case "createWebMessageChannel":
+ if let webView = webView {
+ let _ = webView.createWebMessageChannel { (webMessageChannel) in
+ result(webMessageChannel.toMap())
+ }
+ } else {
+ result(nil)
+ }
+ break
+ case "postWebMessage":
+ if let webView = webView {
+ let message = arguments!["message"] as! [String: Any?]
+ let targetOrigin = arguments!["targetOrigin"] as! String
+
+ var ports: [WebMessagePort] = []
+ let portsMap = message["ports"] as? [[String: Any?]]
+ if let portsMap = portsMap {
+ for portMap in portsMap {
+ let webMessageChannelId = portMap["webMessageChannelId"] as! String
+ let index = portMap["index"] as! Int
+ if let webMessageChannel = webView.webMessageChannels[webMessageChannelId] {
+ ports.append(webMessageChannel.ports[index])
+ }
+ }
+ }
+ let webMessage = WebMessage(data: message["data"] as? String, ports: ports)
+ do {
+ try webView.postWebMessage(message: webMessage, targetOrigin: targetOrigin) { (_) in
+ result(true)
+ }
+ } catch let error as NSError {
+ result(FlutterError(code: "InAppWebViewMethodHandler", message: error.domain, details: nil))
+ }
+ } else {
+ result(false)
+ }
+ break
+ case "addWebMessageListener":
+ if let webView = webView {
+ let webMessageListenerMap = arguments!["webMessageListener"] as! [String: Any?]
+ let webMessageListener = WebMessageListener.fromMap(map: webMessageListenerMap)!
+ do {
+ try webView.addWebMessageListener(webMessageListener: webMessageListener)
+ result(false)
+ } catch let error as NSError {
+ result(FlutterError(code: "InAppWebViewMethodHandler", message: error.domain, details: nil))
+ }
+ } else {
+ result(false)
+ }
+ break
default:
result(FlutterMethodNotImplemented)
break
diff --git a/ios/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift b/ios/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift
index f26f2e92..2d4b7f89 100644
--- a/ios/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift
+++ b/ios/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift
@@ -20,6 +20,7 @@ let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT = PluginScript(
let JAVASCRIPT_BRIDGE_JS_SOURCE = """
window.\(JAVASCRIPT_BRIDGE_NAME) = {};
+\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME) = {};
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() {
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
var _callHandlerID = setTimeout(function(){});
@@ -28,6 +29,7 @@ window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() {
window.\(JAVASCRIPT_BRIDGE_NAME)[_callHandlerID] = resolve;
});
};
+\(WEB_MESSAGE_LISTENER_JS_SOURCE)
"""
let PLATFORM_READY_JS_SOURCE = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));";
diff --git a/ios/Classes/PluginScriptsJS/WebMessageChannelJS.swift b/ios/Classes/PluginScriptsJS/WebMessageChannelJS.swift
new file mode 100644
index 00000000..18445841
--- /dev/null
+++ b/ios/Classes/PluginScriptsJS/WebMessageChannelJS.swift
@@ -0,0 +1,10 @@
+//
+// WebMessageChannelJS.swift
+// flutter_inappwebview
+//
+// Created by Lorenzo Pichilli on 10/03/21.
+//
+
+import Foundation
+
+let WEB_MESSAGE_CHANNELS_VARIABLE_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._webMessageChannels"
diff --git a/ios/Classes/PluginScriptsJS/WebMessageListenerJS.swift b/ios/Classes/PluginScriptsJS/WebMessageListenerJS.swift
new file mode 100644
index 00000000..369ac913
--- /dev/null
+++ b/ios/Classes/PluginScriptsJS/WebMessageListenerJS.swift
@@ -0,0 +1,112 @@
+//
+// WebMessageListenerJS.swift
+// flutter_inappwebview
+//
+// Created by Lorenzo Pichilli on 10/03/21.
+//
+
+import Foundation
+
+let WEB_MESSAGE_LISTENER_JS_SOURCE = """
+function FlutterInAppWebViewWebMessageListener(jsObjectName) {
+ this.jsObjectName = jsObjectName;
+ this.listeners = [];
+ this.onmessage = null;
+}
+FlutterInAppWebViewWebMessageListener.prototype.postMessage = function(message) {
+ window.webkit.messageHandlers['onWebMessageListenerPostMessageReceived'].postMessage({jsObjectName: this.jsObjectName, message: message});
+};
+FlutterInAppWebViewWebMessageListener.prototype.addEventListener = function(type, listener) {
+ if (listener == null) {
+ return;
+ }
+ this.listeners.push(listener);
+};
+FlutterInAppWebViewWebMessageListener.prototype.removeEventListener = function(type, listener) {
+ if (listener == null) {
+ return;
+ }
+ var index = this.listeners.indexOf(listener);
+ if (index >= 0) {
+ this.listeners.splice(index, 1);
+ }
+};
+
+window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6 = function(ip_string) {
+ // replace ipv4 address if any
+ var ipv4 = ip_string.match(/(.*:)([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)/);
+ if (ipv4) {
+ var ip_string = ipv4[1];
+ ipv4 = ipv4[2].match(/[0-9]+/g);
+ for (var i = 0;i < 4;i ++) {
+ var byte = parseInt(ipv4[i],10);
+ ipv4[i] = ("0" + byte.toString(16)).substr(-2);
+ }
+ ip_string += ipv4[0] + ipv4[1] + ':' + ipv4[2] + ipv4[3];
+ }
+
+ // take care of leading and trailing ::
+ ip_string = ip_string.replace(/^:|:$/g, '');
+
+ var ipv6 = ip_string.split(':');
+
+ for (var i = 0; i < ipv6.length; i ++) {
+ var hex = ipv6[i];
+ if (hex != "") {
+ // normalize leading zeros
+ ipv6[i] = ("0000" + hex).substr(-4);
+ }
+ else {
+ // normalize grouped zeros ::
+ hex = [];
+ for (var j = ipv6.length; j <= 8; j ++) {
+ hex.push('0000');
+ }
+ ipv6[i] = hex.join(':');
+ }
+ }
+
+ return ipv6.join(':');
+}
+
+window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed = function(allowedOriginRules, scheme, host, port) {
+ for (var rule of allowedOriginRules) {
+ if (rule === "*") {
+ return true;
+ }
+ if (scheme == null || scheme === "") {
+ continue;
+ }
+ if ((scheme == null || scheme === "") && (host == null || host === "") && (port === 0 || port === "" || port == null)) {
+ continue;
+ }
+ var rulePort = rule.port == null || rule.port === 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port;
+ var currentPort = port === 0 || port === "" || port == null ? (scheme == "https" ? 443 : 80) : port;
+ var IPv6 = null;
+ if (rule.host != null && rule.host[0] === "[") {
+ try {
+ IPv6 = normalizeIPv6(rule.host.substring(1, rule.host.length - 1));
+ } catch {}
+ }
+ var hostIPv6 = null;
+ try {
+ hostIPv6 = normalizeIPv6(host);
+ } catch {}
+
+ var schemeAllowed = scheme == rule.scheme;
+
+ var hostAllowed = rule.host == null ||
+ rule.host === "" ||
+ host === rule.host ||
+ (rule.host[0] === "*" && host != null && host.indexOf(rule.host.split("*")[1]) >= 0) ||
+ (hostIPv6 != null && IPv6 != null && hostIPv6 === IPv6);
+
+ var portAllowed = rulePort === currentPort
+
+ if (schemeAllowed && hostAllowed && portAllowed) {
+ return true;
+ }
+ }
+ return false
+}
+"""
diff --git a/ios/Classes/Types/WebMessage.swift b/ios/Classes/Types/WebMessage.swift
new file mode 100644
index 00000000..169926b2
--- /dev/null
+++ b/ios/Classes/Types/WebMessage.swift
@@ -0,0 +1,27 @@
+//
+// WebMessage.swift
+// flutter_inappwebview
+//
+// Created by Lorenzo Pichilli on 10/03/21.
+//
+
+import Foundation
+
+public class WebMessage : NSObject {
+ var data: String?
+ var ports: [WebMessagePort]?
+
+ public init(data: String?, ports: [WebMessagePort]?) {
+ super.init()
+ self.data = data
+ self.ports = ports
+ }
+
+ public func dispose() {
+ ports?.removeAll()
+ }
+
+ deinit {
+ print("WebMessage - dealloc")
+ }
+}
diff --git a/ios/Classes/Types/WebMessageChannel.swift b/ios/Classes/Types/WebMessageChannel.swift
new file mode 100644
index 00000000..de9e75aa
--- /dev/null
+++ b/ios/Classes/Types/WebMessageChannel.swift
@@ -0,0 +1,150 @@
+//
+// WebMessageChannel.swift
+// flutter_inappwebview
+//
+// Created by Lorenzo Pichilli on 10/03/21.
+//
+
+import Foundation
+
+public class WebMessageChannel : FlutterMethodCallDelegate {
+ var id: String
+ var channel: FlutterMethodChannel?
+ var webView: InAppWebView?
+ var ports: [WebMessagePort] = []
+
+ public init(id: String) {
+ self.id = id
+ super.init()
+ self.channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappwebview_web_message_channel_" + id,
+ binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger())
+ self.channel?.setMethodCallHandler(self.handle)
+ self.ports = [
+ WebMessagePort(name: "port1", webMessageChannel: self),
+ WebMessagePort(name: "port2", webMessageChannel: self)
+ ]
+ }
+
+ public func initJsInstance(webView: InAppWebView, completionHandler: ((WebMessageChannel) -> Void)? = nil) {
+ self.webView = webView
+ if let webView = self.webView {
+ webView.evaluateJavascript(source: """
+ (function() {
+ \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"] = new MessageChannel();
+ })();
+ """) { (_) in
+ completionHandler?(self)
+ }
+ } else {
+ completionHandler?(self)
+ }
+ }
+
+ public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ let arguments = call.arguments as? NSDictionary
+
+ switch call.method {
+ case "setWebMessageCallback":
+ if let _ = webView, ports.count > 0 {
+ let index = arguments!["index"] as! Int
+ let port = ports[index]
+ do {
+ try port.setWebMessageCallback { (_) in
+ result(true)
+ }
+ } catch let error as NSError {
+ result(FlutterError(code: "WebMessageChannel", message: error.domain, details: nil))
+ }
+
+ } else {
+ result(true)
+ }
+ break
+ case "postMessage":
+ if let webView = webView, ports.count > 0 {
+ let index = arguments!["index"] as! Int
+ let port = ports[index]
+ let message = arguments!["message"] as! [String: Any?]
+
+ var webMessagePorts: [WebMessagePort] = []
+ let portsMap = message["ports"] as? [[String: Any?]]
+ if let portsMap = portsMap {
+ for portMap in portsMap {
+ let webMessageChannelId = portMap["webMessageChannelId"] as! String
+ let index = portMap["index"] as! Int
+ if let webMessageChannel = webView.webMessageChannels[webMessageChannelId] {
+ webMessagePorts.append(webMessageChannel.ports[index])
+ }
+ }
+ }
+ let webMessage = WebMessage(data: message["data"] as? String, ports: webMessagePorts)
+ do {
+ try port.postMessage(message: webMessage) { (_) in
+ result(true)
+ }
+ } catch let error as NSError {
+ result(FlutterError(code: "WebMessageChannel", message: error.domain, details: nil))
+ }
+ } else {
+ result(true)
+ }
+ break
+ case "close":
+ if let _ = webView, ports.count > 0 {
+ let index = arguments!["index"] as! Int
+ let port = ports[index]
+ do {
+ try port.close { (_) in
+ result(true)
+ }
+ } catch let error as NSError {
+ result(FlutterError(code: "WebMessageChannel", message: error.domain, details: nil))
+ }
+ } else {
+ result(true)
+ }
+ break
+ default:
+ result(FlutterMethodNotImplemented)
+ break
+ }
+ }
+
+ public func onMessage(index: Int64, message: String?) {
+ let arguments: [String:Any?] = [
+ "index": index,
+ "message": message
+ ]
+ channel?.invokeMethod("onMessage", arguments: arguments)
+ }
+
+ public func toMap () -> [String:Any?] {
+ return [
+ "id": id
+ ]
+ }
+
+ public func dispose() {
+ channel?.setMethodCallHandler(nil)
+ for port in ports {
+ port.dispose()
+ }
+ ports.removeAll()
+ webView?.evaluateJavascript(source: """
+ (function() {
+ var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"];
+ if (webMessageChannel != null) {
+ webMessageChannel.port1.close();
+ webMessageChannel.port2.close();
+ delete \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"];
+ }
+ })();
+ """)
+ channel = nil
+ webView = nil
+ }
+
+ deinit {
+ print("WebMessageChannel - dealloc")
+ }
+}
diff --git a/ios/Classes/Types/WebMessageListener.swift b/ios/Classes/Types/WebMessageListener.swift
new file mode 100644
index 00000000..6447a680
--- /dev/null
+++ b/ios/Classes/Types/WebMessageListener.swift
@@ -0,0 +1,226 @@
+//
+// WebMessageListener.swift
+// flutter_inappwebview
+//
+// Created by Lorenzo Pichilli on 10/03/21.
+//
+
+import Foundation
+import WebKit
+
+public class WebMessageListener : FlutterMethodCallDelegate {
+
+ var jsObjectName: String
+ var allowedOriginRules: Set
+ var channel: FlutterMethodChannel?
+ var webView: InAppWebView?
+
+ public init(jsObjectName: String, allowedOriginRules: Set) {
+ self.jsObjectName = jsObjectName
+ self.allowedOriginRules = allowedOriginRules
+ super.init()
+ self.channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappwebview_web_message_listener_" + self.jsObjectName,
+ binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger())
+ self.channel?.setMethodCallHandler(self.handle)
+ }
+
+ public func assertOriginRulesValid() throws {
+ for (index, originRule) in allowedOriginRules.enumerated() {
+ if originRule.isEmpty {
+ throw NSError(domain: "allowedOriginRules[\(index)] is empty", code: 0)
+ }
+ if originRule == "*" {
+ continue
+ }
+ if let url = URL(string: originRule) {
+ guard let scheme = url.scheme else {
+ throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
+ }
+ if scheme == "http" || scheme == "https", url.host == nil || url.host!.isEmpty {
+ throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
+ }
+ if scheme != "http", scheme != "https", url.host != nil || url.port != nil {
+ throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
+ }
+ if url.host == nil || url.host!.isEmpty, url.port != nil {
+ throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
+ }
+ if !url.path.isEmpty {
+ throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
+ }
+ if let hostname = url.host {
+ if let firstIndex = hostname.firstIndex(of: "*") {
+ let distance = hostname.distance(from: hostname.startIndex, to: firstIndex)
+ if distance != 0 || (distance == 0 && hostname.prefix(2) != "*.") {
+ throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
+ }
+ }
+ if hostname.hasPrefix("[") {
+ if !hostname.hasSuffix("]") {
+ throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
+ }
+ let fromIndex = hostname.index(hostname.startIndex, offsetBy: 1)
+ let toIndex = hostname.index(hostname.startIndex, offsetBy: hostname.count - 1)
+ let indexRange = Range(uncheckedBounds: (lower: fromIndex, upper: toIndex))
+ let ipv6 = String(hostname[indexRange])
+ if !Util.isIPv6(address: ipv6) {
+ throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
+ }
+ }
+ }
+ } else {
+ throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
+ }
+ }
+ }
+
+ public func initJsInstance(webView: InAppWebView) {
+ self.webView = webView
+ if let webView = self.webView {
+ let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'")
+ let allowedOriginRulesString = allowedOriginRules.map { (allowedOriginRule) -> String in
+ if allowedOriginRule == "*" {
+ return "'*'"
+ }
+ let rule = URL(string: allowedOriginRule)!
+ return """
+ {scheme: '\(rule.scheme!)', host: '\(rule.host?.replacingOccurrences(of: "\'", with: "\\'") ?? "null")', port: \(rule.port != nil ? String(rule.port!) : "null")}
+ """
+ }.joined(separator: ", ")
+ let source = """
+ (function() {
+ var allowedOriginRules = [\(allowedOriginRulesString)];
+ var isPageBlank = window.location.href === "about:blank";
+ var scheme = !isPageBlank ? window.location.protocol.replace(":", "") : null;
+ var host = !isPageBlank ? window.location.hostname : null;
+ var port = !isPageBlank ? window.location.port : null;
+ if (window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed(allowedOriginRules, scheme, host, port)) {
+ window['\(jsObjectNameEscaped)'] = new FlutterInAppWebViewWebMessageListener('\(jsObjectNameEscaped)');
+ }
+ })();
+ """
+ webView.configuration.userContentController.addPluginScript(PluginScript(
+ groupName: "WebMessageListener-" + jsObjectName,
+ source: source,
+ injectionTime: .atDocumentStart,
+ forMainFrameOnly: false,
+ requiredInAllContentWorlds: false,
+ messageHandlerNames: ["onWebMessageListenerPostMessageReceived"]
+ ))
+ webView.configuration.userContentController.sync(scriptMessageHandler: webView)
+ }
+ }
+
+ public static func fromMap(map: [String:Any?]?) -> WebMessageListener? {
+ guard let map = map else {
+ return nil
+ }
+ return WebMessageListener(
+ jsObjectName: map["jsObjectName"] as! String,
+ allowedOriginRules: Set(map["allowedOriginRules"] as! [String])
+ )
+ }
+
+ public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ let arguments = call.arguments as? NSDictionary
+
+ switch call.method {
+ case "postMessage":
+ if let webView = webView {
+ let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'")
+ let messageEscaped = (arguments!["message"] as! String).replacingOccurrences(of: "\'", with: "\\'")
+ let source = """
+ (function() {
+ var webMessageListener = window['\(jsObjectNameEscaped)'];
+ if (webMessageListener != null) {
+ var event = {data: '\(messageEscaped)'};
+ if (webMessageListener.onmessage != null) {
+ webMessageListener.onmessage(event);
+ } else {
+ for (var listener of webMessageListener.listeners) {
+ listener(event);
+ }
+ }
+ }
+ })();
+ """
+ webView.evaluateJavascript(source: source) { (_) in
+ result(true)
+ }
+ } else {
+ result(true)
+ }
+ break
+ default:
+ result(FlutterMethodNotImplemented)
+ break
+ }
+ }
+
+ public func isOriginAllowed(scheme: String?, host: String?, port: Int?) -> Bool {
+ for allowedOriginRule in allowedOriginRules {
+ if allowedOriginRule == "*" {
+ return true
+ }
+ if scheme == nil || scheme!.isEmpty {
+ continue
+ }
+ if scheme == nil || scheme!.isEmpty, host == nil || host!.isEmpty, port == nil || port == 0 {
+ continue
+ }
+ if let rule = URL(string: allowedOriginRule) {
+ let rulePort = rule.port == nil || rule.port == 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port!
+ let currentPort = port == nil || port == 0 ? (scheme == "https" ? 443 : 80) : port!
+ var IPv6: String? = nil
+ if let hostname = rule.host, hostname.hasPrefix("[") {
+ let fromIndex = hostname.index(hostname.startIndex, offsetBy: 1)
+ let toIndex = hostname.index(hostname.startIndex, offsetBy: hostname.count - 1)
+ let indexRange = Range(uncheckedBounds: (lower: fromIndex, upper: toIndex))
+ do {
+ IPv6 = try Util.normalizeIPv6(address: String(hostname[indexRange]))
+ } catch {}
+ }
+ var hostIPv6: String? = nil
+ if let host = host, Util.isIPv6(address: host) {
+ do {
+ hostIPv6 = try Util.normalizeIPv6(address: host)
+ } catch {}
+ }
+
+ let schemeAllowed = scheme != nil && !scheme!.isEmpty && scheme == rule.scheme
+
+ let hostAllowed = rule.host == nil ||
+ rule.host!.isEmpty ||
+ host == rule.host ||
+ (rule.host!.hasPrefix("*") && host != nil && host!.hasSuffix(rule.host!.split(separator: "*", omittingEmptySubsequences: false)[1])) ||
+ (hostIPv6 != nil && IPv6 != nil && hostIPv6 == IPv6)
+
+ let portAllowed = rulePort == currentPort
+
+ if schemeAllowed, hostAllowed, portAllowed {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ public func onPostMessage(message: String?, sourceOrigin: URL?, isMainFrame: Bool) {
+ let arguments: [String:Any?] = [
+ "message": message,
+ "sourceOrigin": sourceOrigin?.absoluteString,
+ "isMainFrame": isMainFrame
+ ]
+ channel?.invokeMethod("onPostMessage", arguments: arguments)
+ }
+
+ public func dispose() {
+ channel?.setMethodCallHandler(nil)
+ channel = nil
+ webView = nil
+ }
+
+ deinit {
+ print("WebMessageListener - dealloc")
+ }
+}
diff --git a/ios/Classes/Types/WebMessagePort.swift b/ios/Classes/Types/WebMessagePort.swift
new file mode 100644
index 00000000..dff16845
--- /dev/null
+++ b/ios/Classes/Types/WebMessagePort.swift
@@ -0,0 +1,122 @@
+//
+// WebMessagePort.swift
+// flutter_inappwebview
+//
+// Created by Lorenzo Pichilli on 10/03/21.
+//
+
+import Foundation
+
+public class WebMessagePort : NSObject {
+ var name: String
+ var webMessageChannel: WebMessageChannel?
+ var isClosed = false
+ var isTransferred = false
+ var isStarted = false
+
+ public init(name: String, webMessageChannel: WebMessageChannel) {
+ self.name = name
+ super.init()
+ self.webMessageChannel = webMessageChannel
+ }
+
+ public func setWebMessageCallback(completionHandler: ((Any?) -> Void)? = nil) throws {
+ if isClosed || isTransferred {
+ throw NSError(domain: "Port is already closed or transferred", code: 0)
+ }
+ self.isStarted = true
+ if let webMessageChannel = webMessageChannel, let webView = webMessageChannel.webView {
+ let index = name == "port1" ? 0 : 1
+ webView.evaluateJavascript(source: """
+ (function() {
+ var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"];
+ if (webMessageChannel != null) {
+ webMessageChannel.\(self.name).onmessage = function (event) {
+ window.webkit.messageHandlers["onWebMessagePortMessageReceived"].postMessage({
+ "webMessageChannelId": "\(webMessageChannel.id)",
+ "index": \(String(index)),
+ "message": event.data
+ });
+ }
+ }
+ })();
+ """) { (_) in
+ completionHandler?(nil)
+ }
+ } else {
+ completionHandler?(nil)
+ }
+ }
+
+ public func postMessage(message: WebMessage, completionHandler: ((Any?) -> Void)? = nil) throws {
+ if isClosed || isTransferred {
+ throw NSError(domain: "Port is already closed or transferred", code: 0)
+ }
+ if let webMessageChannel = webMessageChannel, let webView = webMessageChannel.webView {
+ var portsString = "null"
+ if let ports = message.ports {
+ var portArrayString: [String] = []
+ for port in ports {
+ if port == self {
+ throw NSError(domain: "Source port cannot be transferred", code: 0)
+ }
+ if port.isStarted {
+ throw NSError(domain: "Port is already started", code: 0)
+ }
+ if port.isClosed || port.isTransferred {
+ throw NSError(domain: "Port is already closed or transferred", code: 0)
+ }
+ port.isTransferred = true
+ portArrayString.append("\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)['\(port.webMessageChannel!.id)'].\(port.name)")
+ }
+ portsString = "[" + portArrayString.joined(separator: ", ") + "]"
+ }
+ let data = message.data?.replacingOccurrences(of: "\'", with: "\\'") ?? "null"
+ let source = """
+ (function() {
+ var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"];
+ if (webMessageChannel != null) {
+ webMessageChannel.\(self.name).postMessage('\(data)', \(portsString));
+ }
+ })();
+ """
+ webView.evaluateJavascript(source: source) { (_) in
+ completionHandler?(nil)
+ }
+ } else {
+ completionHandler?(nil)
+ }
+ message.dispose()
+ }
+
+ public func close(completionHandler: ((Any?) -> Void)? = nil) throws {
+ if isTransferred {
+ throw NSError(domain: "Port is already transferred", code: 0)
+ }
+ isClosed = true
+ if let webMessageChannel = webMessageChannel, let webView = webMessageChannel.webView {
+ let source = """
+ (function() {
+ var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(webMessageChannel.id)"];
+ if (webMessageChannel != null) {
+ webMessageChannel.\(self.name).close();
+ }
+ })();
+ """
+ webView.evaluateJavascript(source: source) { (_) in
+ completionHandler?(nil)
+ }
+ } else {
+ completionHandler?(nil)
+ }
+ }
+
+ public func dispose() {
+ isClosed = true
+ webMessageChannel = nil
+ }
+
+ deinit {
+ print("WebMessagePort - dealloc")
+ }
+}
diff --git a/ios/Classes/Util.swift b/ios/Classes/Util.swift
index 4899c12f..121930ab 100644
--- a/ios/Classes/Util.swift
+++ b/ios/Classes/Util.swift
@@ -149,4 +149,78 @@ public class Util {
return "NORMAL"
}
}
+
+ public static func isIPv4(address: String) -> Bool {
+ var sin = sockaddr_in()
+ return address.withCString({ cstring in inet_pton(AF_INET, cstring, &sin.sin_addr) }) == 1
+ }
+
+ public static func isIPv6(address: String) -> Bool {
+ var sin6 = sockaddr_in6()
+ return address.withCString({ cstring in inet_pton(AF_INET6, cstring, &sin6.sin6_addr) }) == 1
+ }
+
+ public static func isIpAddress(address: String) -> Bool {
+ return Util.isIPv6(address: address) || Util.isIPv4(address: address)
+ }
+
+ public static func normalizeIPv6(address: String) throws -> String {
+ if !Util.isIPv6(address: address) {
+ throw NSError(domain: "Invalid address: \(address)", code: 0)
+ }
+ var ipString = address
+ // replace ipv4 address if any
+ let ipv4Regex = try! NSRegularExpression(pattern: "(.*:)([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)")
+ if let match = ipv4Regex.firstMatch(in: address, options: [], range: NSRange(location: 0, length: address.utf16.count)) {
+ if let ipv6PartRange = Range(match.range(at: 1), in: address) {
+ ipString = String(address[ipv6PartRange])
+ }
+ if let ipv4Range = Range(match.range(at: 2), in: address) {
+ let ipv4 = address[ipv4Range]
+ let ipv4Splitted = ipv4.split(separator: ".")
+ var ipv4Converted = Array(repeating: "0000", count: 4)
+ for i in 0...3 {
+ let byte = Int(ipv4Splitted[i])!
+ let hex = ("0" + String(byte, radix: 16))
+ var offset = hex.count - 3
+ offset = offset < 0 ? 0 : offset
+ let fromIndex = hex.index(hex.startIndex, offsetBy: offset)
+ let toIndex = hex.index(hex.startIndex, offsetBy: hex.count - 1)
+ let indexRange = Range(uncheckedBounds: (lower: fromIndex, upper: toIndex))
+ ipv4Converted[i] = String(hex[indexRange])
+ }
+ ipString += ipv4Converted[0] + ipv4Converted[1] + ":" + ipv4Converted[2] + ipv4Converted[3]
+ }
+ }
+
+ // take care of leading and trailing ::
+ let regex = try! NSRegularExpression(pattern: "^:|:$")
+ ipString = regex.stringByReplacingMatches(in: ipString, options: [], range: NSRange(location: 0, length: ipString.count), withTemplate: "")
+
+ let ipv6 = ipString.split(separator: ":", omittingEmptySubsequences: false)
+ var fullIPv6 = Array(repeating: "0000", count: ipv6.count)
+
+ for (i, hex) in ipv6.enumerated() {
+ if !hex.isEmpty {
+ // normalize leading zeros
+ let hexString = String("0000" + hex)
+ var offset = hexString.count - 5
+ offset = offset < 0 ? 0 : offset
+ let fromIndex = hexString.index(hexString.startIndex, offsetBy: offset)
+ let toIndex = hexString.index(hexString.startIndex, offsetBy: hexString.count - 1)
+ let indexRange = Range(uncheckedBounds: (lower: fromIndex, upper: toIndex))
+ fullIPv6[i] = String(hexString[indexRange])
+ } else {
+ // normalize grouped zeros ::
+ var zeros: [String] = []
+ for j in ipv6.count...8 {
+ zeros.append("0000")
+ }
+ fullIPv6[i] = zeros.joined(separator: ":")
+ }
+ }
+
+ return fullIPv6.joined(separator: ":")
+ }
+
}
diff --git a/lib/src/chrome_safari_browser/chrome_safari_browser.dart b/lib/src/chrome_safari_browser/chrome_safari_browser.dart
index 47818b2c..a95b18a2 100755
--- a/lib/src/chrome_safari_browser/chrome_safari_browser.dart
+++ b/lib/src/chrome_safari_browser/chrome_safari_browser.dart
@@ -45,7 +45,7 @@ class ChromeSafariBrowser {
const MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser');
ChromeSafariBrowser() {
- id = ViewIdGenerator.generateId();
+ id = IdGenerator.generate();
this._channel =
MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser_$id');
this._channel.setMethodCallHandler(handleMethod);
diff --git a/lib/src/in_app_browser/in_app_browser.dart b/lib/src/in_app_browser/in_app_browser.dart
index e15f29db..f6a70682 100755
--- a/lib/src/in_app_browser/in_app_browser.dart
+++ b/lib/src/in_app_browser/in_app_browser.dart
@@ -66,7 +66,7 @@ class InAppBrowser {
///
InAppBrowser({this.windowId, this.initialUserScripts}) {
- id = ViewIdGenerator.generateId();
+ id = IdGenerator.generate();
this._channel =
MethodChannel('com.pichillilorenzo/flutter_inappbrowser_$id');
this._channel.setMethodCallHandler(handleMethod);
diff --git a/lib/src/in_app_webview/android/in_app_webview_controller.dart b/lib/src/in_app_webview/android/in_app_webview_controller.dart
index b2411d98..4df59218 100644
--- a/lib/src/in_app_webview/android/in_app_webview_controller.dart
+++ b/lib/src/in_app_webview/android/in_app_webview_controller.dart
@@ -176,7 +176,7 @@ class AndroidInAppWebViewController {
///has loaded WebView will be killed.
///The next time the app starts and loads WebView it will use the new WebView package instead.
///
- ///**NOTE**: available only on Android 26+.
+ ///**NOTE**: available only on Android 21+.
///
///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewCompat#getCurrentWebViewPackage(android.content.Context)
static Future getCurrentWebViewPackage() async {
diff --git a/lib/src/in_app_webview/headless_in_app_webview.dart b/lib/src/in_app_webview/headless_in_app_webview.dart
index ffaae21d..754ddc3e 100644
--- a/lib/src/in_app_webview/headless_in_app_webview.dart
+++ b/lib/src/in_app_webview/headless_in_app_webview.dart
@@ -91,7 +91,7 @@ class HeadlessInAppWebView implements WebView {
this.contextMenu,
this.initialUserScripts,
this.pullToRefreshController}) {
- id = ViewIdGenerator.generateId();
+ id = IdGenerator.generate();
webViewController = new InAppWebViewController(id, this);
}
diff --git a/lib/src/in_app_webview/in_app_webview_controller.dart b/lib/src/in_app_webview/in_app_webview_controller.dart
index 33118654..59319230 100644
--- a/lib/src/in_app_webview/in_app_webview_controller.dart
+++ b/lib/src/in_app_webview/in_app_webview_controller.dart
@@ -18,6 +18,8 @@ import '../types.dart';
import '../in_app_browser/in_app_browser.dart';
import '../web_storage/web_storage.dart';
import '../util.dart';
+import '../web_message/web_message_channel.dart';
+import '../web_message/web_message_listener.dart';
import 'headless_in_app_webview.dart';
import 'in_app_webview.dart';
@@ -51,6 +53,7 @@ class InAppWebViewController {
Map javaScriptHandlersMap =
HashMap();
List _userScripts = [];
+ Set _webMessageListenerObjNames = Set();
// ignore: unused_field
dynamic _id;
@@ -1455,7 +1458,7 @@ class InAppWebViewController {
void addJavaScriptHandler(
{required String handlerName,
required JavaScriptHandlerCallback callback}) {
- assert(!_JAVASCRIPT_HANDLER_FORBIDDEN_NAMES.contains(handlerName));
+ assert(!_JAVASCRIPT_HANDLER_FORBIDDEN_NAMES.contains(handlerName), '"$handlerName" is a forbidden name!');
this.javaScriptHandlersMap[handlerName] = (callback);
}
@@ -1467,7 +1470,7 @@ class InAppWebViewController {
return this.javaScriptHandlersMap.remove(handlerName);
}
- ///Takes a screenshot (in PNG format) of the WebView's visible viewport and returns a [Uint8List]. Returns `null` if it wasn't be able to take it.
+ ///Takes a screenshot of the WebView's visible viewport and returns a [Uint8List]. Returns `null` if it wasn't be able to take it.
///
///[screenshotConfiguration] represents the configuration data to use when generating an image from a web view’s contents.
///
@@ -2087,6 +2090,32 @@ class InAppWebViewController {
return await _channel.invokeMethod('isSecureContext', args);
}
+ Future createWebMessageChannel() async {
+ Map args = {};
+ Map? result = (await _channel.invokeMethod('createWebMessageChannel', args))
+ ?.cast();
+ return WebMessageChannel.fromMap(result);
+ }
+
+ Future postWebMessage({required WebMessage message, Uri? targetOrigin}) async {
+ if (targetOrigin == null) {
+ targetOrigin = Uri.parse("");
+ }
+ Map args = {};
+ args.putIfAbsent('message', () => message.toMap());
+ args.putIfAbsent('targetOrigin', () => targetOrigin.toString());
+ await _channel.invokeMethod('postWebMessage', args);
+ }
+
+ Future addWebMessageListener(WebMessageListener webMessageListener) async {
+ assert(!_webMessageListenerObjNames.contains(webMessageListener.jsObjectName), "jsObjectName ${webMessageListener.jsObjectName} was already added.");
+ _webMessageListenerObjNames.add(webMessageListener.jsObjectName);
+
+ Map args = {};
+ args.putIfAbsent('webMessageListener', () => webMessageListener.toMap());
+ await _channel.invokeMethod('addWebMessageListener', args);
+ }
+
///Gets the default user agent.
///
///**Official Android API**: https://developer.android.com/reference/android/webkit/WebSettings#getDefaultUserAgent(android.content.Context)
diff --git a/lib/src/main.dart b/lib/src/main.dart
index d692b27d..d3a77fd3 100644
--- a/lib/src/main.dart
+++ b/lib/src/main.dart
@@ -15,3 +15,4 @@ export 'content_blocker.dart';
export 'http_auth_credentials_database.dart';
export 'context_menu.dart';
export 'pull_to_refresh/main.dart';
+export 'web_message/main.dart';
diff --git a/lib/src/types.dart b/lib/src/types.dart
index ff9651c5..2cd130a0 100755
--- a/lib/src/types.dart
+++ b/lib/src/types.dart
@@ -6147,8 +6147,10 @@ class IOSURLResponse {
///The name of the text encoding provided by the response’s originating source.
String? textEncodingName;
+ ///All HTTP header fields of the response.
Map? headers;
+ ///The response’s HTTP status code.
int? statusCode;
IOSURLResponse(
diff --git a/lib/src/util.dart b/lib/src/util.dart
index c6bfc3c1..9cf8b9dd 100644
--- a/lib/src/util.dart
+++ b/lib/src/util.dart
@@ -3,7 +3,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart';
-class ViewIdGenerator {
+class IdGenerator {
static int _count = 0;
/// Math.Random()-based RNG. All platforms, fast, not cryptographically strong. Optional Seed passable.
@@ -31,7 +31,7 @@ class ViewIdGenerator {
return b;
}
- static String generateId() {
+ static String generate() {
_count++;
return _count.toString() + cryptoRNG().map((e) => e.toString()).join('');
}
diff --git a/lib/src/web_message/main.dart b/lib/src/web_message/main.dart
new file mode 100644
index 00000000..036f08b1
--- /dev/null
+++ b/lib/src/web_message/main.dart
@@ -0,0 +1,2 @@
+export 'web_message_channel.dart';
+export 'web_message_listener.dart';
\ No newline at end of file
diff --git a/lib/src/web_message/web_message_channel.dart b/lib/src/web_message/web_message_channel.dart
new file mode 100644
index 00000000..9938c617
--- /dev/null
+++ b/lib/src/web_message/web_message_channel.dart
@@ -0,0 +1,115 @@
+import 'package:flutter/services.dart';
+
+class WebMessageChannel {
+ String id;
+ WebMessagePort port1;
+ WebMessagePort port2;
+
+ late MethodChannel _channel;
+
+ WebMessageChannel({required this.id, required this.port1, required this.port2}) {
+ this._channel = MethodChannel(
+ 'com.pichillilorenzo/flutter_inappwebview_web_message_channel_$id');
+ this._channel.setMethodCallHandler(handleMethod);
+ }
+
+ static WebMessageChannel? fromMap(Map? map) {
+ if (map == null) {
+ return null;
+ }
+ var webMessageChannel = WebMessageChannel(
+ id: map["id"],
+ port1: WebMessagePort(index: 0),
+ port2: WebMessagePort(index: 1)
+ );
+ webMessageChannel.port1._webMessageChannel = webMessageChannel;
+ webMessageChannel.port2._webMessageChannel = webMessageChannel;
+ return webMessageChannel;
+ }
+
+ Future handleMethod(MethodCall call) async {
+ switch (call.method) {
+ case "onMessage":
+ int index = call.arguments["index"];
+ var port = index == 0 ? this.port1 : this.port2;
+ if (port._onMessage != null) {
+ String? message = call.arguments["message"];
+ port._onMessage!(message);
+ }
+ break;
+ default:
+ throw UnimplementedError("Unimplemented ${call.method} method");
+ }
+ return null;
+ }
+}
+
+class WebMessagePort {
+ late final int _index;
+
+ Function(String? message)? _onMessage;
+ late WebMessageChannel _webMessageChannel;
+
+ WebMessagePort({required int index}) {
+ this._index = index;
+ }
+
+ Future setWebMessageCallback(Function(String? message)? onMessage) async {
+ Map args = {};
+ args.putIfAbsent('index', () => this._index);
+ await _webMessageChannel._channel.invokeMethod('setWebMessageCallback', args);
+ this._onMessage = onMessage;
+ }
+
+ Future postMessage(WebMessage message) async {
+ Map args = {};
+ args.putIfAbsent('index', () => this._index);
+ args.putIfAbsent('message', () => message.toMap());
+ await _webMessageChannel._channel.invokeMethod('postMessage', args);
+ }
+
+ Future close() async {
+ Map args = {};
+ args.putIfAbsent('index', () => this._index);
+ await _webMessageChannel._channel.invokeMethod('close', args);
+ }
+
+ Map toMap() {
+ return {
+ "index": this._index,
+ "webMessageChannelId": this._webMessageChannel.id
+ };
+ }
+
+ Map toJson() {
+ return this.toMap();
+ }
+
+ @override
+ String toString() {
+ return toMap().toString();
+ }
+}
+
+class WebMessage {
+ String? data;
+ List? ports;
+
+ WebMessage({this.data, this.ports});
+
+ Map toMap() {
+ return {
+ "data": this.data,
+ "ports": this.ports?.map((e) => e.toMap()).toList(),
+ };
+ }
+
+ Map toJson() {
+ return this.toMap();
+ }
+
+ @override
+ String toString() {
+ return toMap().toString();
+ }
+}
\ No newline at end of file
diff --git a/lib/src/web_message/web_message_listener.dart b/lib/src/web_message/web_message_listener.dart
new file mode 100644
index 00000000..91b811c3
--- /dev/null
+++ b/lib/src/web_message/web_message_listener.dart
@@ -0,0 +1,67 @@
+import 'package:flutter/services.dart';
+
+class WebMessageListener {
+ String jsObjectName;
+ late Set allowedOriginRules;
+ JavaScriptReplyProxy? _replyProxy;
+ Function(String? message, Uri? sourceOrigin, bool isMainFrame, JavaScriptReplyProxy replyProxy)? onPostMessage;
+
+ late MethodChannel _channel;
+
+ WebMessageListener({required this.jsObjectName, Set? allowedOriginRules, this.onPostMessage}) {
+ this.allowedOriginRules = allowedOriginRules != null ? allowedOriginRules : Set.from(["*"]);
+ assert(!this.allowedOriginRules.contains(""), "allowedOriginRules cannot contain empty strings");
+ this._channel = MethodChannel(
+ 'com.pichillilorenzo/flutter_inappwebview_web_message_listener_$jsObjectName');
+ this._channel.setMethodCallHandler(handleMethod);
+ }
+
+ Future handleMethod(MethodCall call) async {
+ switch (call.method) {
+ case "onPostMessage":
+ if (_replyProxy == null) {
+ _replyProxy = new JavaScriptReplyProxy(this);
+ }
+ if (onPostMessage != null) {
+ String? message = call.arguments["message"];
+ Uri? sourceOrigin = call.arguments["sourceOrigin"] != null ? Uri.parse(call.arguments["sourceOrigin"]) : null;
+ bool isMainFrame = call.arguments["isMainFrame"];
+ onPostMessage!(message, sourceOrigin, isMainFrame, _replyProxy!);
+ }
+ break;
+ default:
+ throw UnimplementedError("Unimplemented ${call.method} method");
+ }
+ return null;
+ }
+
+ Map toMap() {
+ return {
+ "jsObjectName": jsObjectName,
+ "allowedOriginRules": allowedOriginRules.toList(),
+ };
+ }
+
+ Map toJson() {
+ return this.toMap();
+ }
+
+ @override
+ String toString() {
+ return toMap().toString();
+ }
+}
+
+class JavaScriptReplyProxy {
+ late WebMessageListener _webMessageListener;
+
+ JavaScriptReplyProxy(WebMessageListener webMessageListener) {
+ this._webMessageListener = webMessageListener;
+ }
+
+ Future postMessage(String message) async {
+ Map args = {};
+ args.putIfAbsent('message', () => message);
+ await _webMessageListener._channel.invokeMethod('postMessage', args);
+ }
+}
diff --git a/nodejs_server_test_auth_basic_and_ssl/index.js b/nodejs_server_test_auth_basic_and_ssl/index.js
index 02741082..87ac1ef7 100755
--- a/nodejs_server_test_auth_basic_and_ssl/index.js
+++ b/nodejs_server_test_auth_basic_and_ssl/index.js
@@ -10,10 +10,10 @@ const appAuthBasic = express()
const fs = require('fs')
const path = require('path')
-var options = {
- key: fs.readFileSync('server-key.pem'),
- cert: fs.readFileSync('server-crt.pem'),
- ca: fs.readFileSync('ca-crt.pem'),
+var options = {
+ key: fs.readFileSync('server-key.pem'),
+ cert: fs.readFileSync('server-crt.pem'),
+ ca: fs.readFileSync('ca-crt.pem'),
requestCert: true,
rejectUnauthorized: false
};
diff --git a/pubspec.yaml b/pubspec.yaml
index af801015..592b6056 100755
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: flutter_inappwebview
description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window.
-version: 5.1.0+4
+version: 5.2.0
homepage: https://github.com/pichillilorenzo/flutter_inappwebview
environment: