diff --git a/CHANGELOG.md b/CHANGELOG.md
index 475b5028..53bf87f1 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
- Deprecated old classes/properties/methods to make them eventually compatible with other Platforms and WebView engines.
- Added Web support
- Added `ProxyController` for Android
+- Added `WebAuthenticationSession` for iOS
- Added `pauseAllMediaPlayback`, `setAllMediaPlaybackSuspended`, `closeAllMediaPresentations`, `requestMediaPlaybackState`, `isInFullscreen`, `getCameraCaptureState`, `setCameraCaptureState`, `getMicrophoneCaptureState`, `setMicrophoneCaptureState` WebView controller methods
- Added `underPageBackgroundColor`, `isTextInteractionEnabled`, `isSiteSpecificQuirksModeEnabled`, `upgradeKnownHostsToHTTPS`, `forceDarkStrategy` WebView settings
- Added `onCameraCaptureStateChanged`, `onMicrophoneCaptureStateChanged` WebView events
@@ -11,7 +12,6 @@
- Updated `getMetaThemeColor` on iOS 15.0+
- Deprecated `onLoadError` for `onReceivedError`. `onReceivedError` will be called also for subframes
- Deprecated `onLoadHttpError` for `onReceivedError`. `onReceivedHttpError` will be called also for subframes
-- Deprecated `onLoadResourceCustomScheme` for `onLoadResourceWithCustomScheme`
### BREAKING CHANGES
diff --git a/example/assets/web-auth.html b/example/assets/web-auth.html
new file mode 100755
index 00000000..37716b14
--- /dev/null
+++ b/example/assets/web-auth.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ Web Auth
+
+
+
+
+
\ No newline at end of file
diff --git a/example/lib/headless_in_app_webview.screen.dart b/example/lib/headless_in_app_webview.screen.dart
index f460b659..8cc3c185 100755
--- a/example/lib/headless_in_app_webview.screen.dart
+++ b/example/lib/headless_in_app_webview.screen.dart
@@ -1,6 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'main.dart';
@@ -86,12 +85,14 @@ class _HeadlessInAppWebViewExampleScreenState
Center(
child: ElevatedButton(
onPressed: () async {
- try {
+ if (headlessWebView?.isRunning() ?? false) {
await headlessWebView?.webViewController.evaluateJavascript(
source: """console.log('Here is the message!');""");
- } on MissingPluginException {
- print(
- "HeadlessInAppWebView is not running. Click on \"Run HeadlessInAppWebView\"!");
+ } else {
+ ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ content: Text(
+ 'HeadlessInAppWebView is not running. Click on "Run HeadlessInAppWebView"!'),
+ ));
}
},
child: Text("Send console.log message")),
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 66fad45d..9550c8f1 100755
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -8,10 +8,11 @@ import 'package:flutter_inappwebview_example/chrome_safari_browser_example.scree
import 'package:flutter_inappwebview_example/headless_in_app_webview.screen.dart';
import 'package:flutter_inappwebview_example/in_app_webiew_example.screen.dart';
import 'package:flutter_inappwebview_example/in_app_browser_example.screen.dart';
+import 'package:flutter_inappwebview_example/web_authentication_session_example.screen.dart';
// import 'package:path_provider/path_provider.dart';
// import 'package:permission_handler/permission_handler.dart';
-// InAppLocalhostServer localhostServer = new InAppLocalhostServer();
+InAppLocalhostServer localhostServer = new InAppLocalhostServer();
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
@@ -25,6 +26,8 @@ Future main() async {
await InAppWebViewController.setWebContentsDebuggingEnabled(true);
}
+ await localhostServer.start();
+
runApp(MyApp());
}
@@ -51,6 +54,12 @@ Drawer myDrawer({required BuildContext context}) {
Navigator.pushReplacementNamed(context, '/ChromeSafariBrowser');
},
),
+ ListTile(
+ title: Text('WebAuthenticationSession'),
+ onTap: () {
+ Navigator.pushReplacementNamed(context, '/WebAuthenticationSession');
+ },
+ ),
ListTile(
title: Text('InAppWebView'),
onTap: () {
@@ -91,6 +100,7 @@ class _MyAppState extends State {
'/InAppBrowser': (context) => InAppBrowserExampleScreen(),
'/ChromeSafariBrowser': (context) => ChromeSafariBrowserExampleScreen(),
'/HeadlessInAppWebView': (context) => HeadlessInAppWebViewExampleScreen(),
+ '/WebAuthenticationSession': (context) => WebAuthenticationSessionExampleScreen(),
});
}
}
diff --git a/example/lib/web_authentication_session_example.screen.dart b/example/lib/web_authentication_session_example.screen.dart
new file mode 100755
index 00000000..bb88d8b0
--- /dev/null
+++ b/example/lib/web_authentication_session_example.screen.dart
@@ -0,0 +1,108 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_inappwebview/flutter_inappwebview.dart';
+
+import 'main.dart';
+
+class WebAuthenticationSessionExampleScreen extends StatefulWidget {
+ @override
+ _WebAuthenticationSessionExampleScreenState createState() =>
+ _WebAuthenticationSessionExampleScreenState();
+}
+
+class _WebAuthenticationSessionExampleScreenState
+ extends State {
+ WebAuthenticationSession? session;
+ String? token;
+
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ session?.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(
+ "WebAuthenticationSession",
+ )),
+ drawer: myDrawer(context: context),
+ body: SafeArea(
+ child: Column(children: [
+ Center(
+ child: Container(
+ padding: EdgeInsets.all(20.0),
+ child: Text("Token: $token"),
+ )),
+ session != null
+ ? Container()
+ : Center(
+ child: ElevatedButton(
+ onPressed: () async {
+ if (session == null &&
+ !kIsWeb &&
+ defaultTargetPlatform == TargetPlatform.iOS &&
+ await WebAuthenticationSession.isAvailable()) {
+ session = await WebAuthenticationSession.create(
+ url: Uri.parse(
+ "http://localhost:8080/assets/web-auth.html"),
+ callbackURLScheme: "test",
+ onComplete: (url, error) async {
+ if (url != null) {
+ setState(() {
+ token = url.queryParameters["token"];
+ });
+ }
+ });
+ setState(() {});
+ } else {
+ ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ content: Text(
+ 'Cannot create Web Authentication Session!'),
+ ));
+ }
+ },
+ child: Text("Create Web Auth Session")),
+ ),
+ session == null
+ ? Container()
+ : Center(
+ child: ElevatedButton(
+ onPressed: () async {
+ var started = false;
+ if (await session?.canStart() ?? false) {
+ started = await session?.start() ?? false;
+ }
+ if (!started) {
+ ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ content: Text(
+ 'Cannot start Web Authentication Session!'),
+ ));
+ }
+ },
+ child: Text("Start Web Auth Session")),
+ ),
+ session == null
+ ? Container()
+ : Center(
+ child: ElevatedButton(
+ onPressed: () async {
+ await session?.dispose();
+ setState(() {
+ token = null;
+ session = null;
+ });
+ },
+ child: Text("Dispose Web Auth Session")),
+ )
+ ]),
+ ));
+ }
+}
diff --git a/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift b/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift
index d4f726aa..1c4a3b79 100755
--- a/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift
+++ b/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift
@@ -27,7 +27,7 @@ public class ChromeSafariBrowserManager: ChannelDelegate {
switch call.method {
case "open":
- let id: String = arguments!["id"] as! String
+ let id = arguments!["id"] as! String
let url = arguments!["url"] as! String
let settings = arguments!["settings"] as! [String: Any?]
let menuItemList = arguments!["menuItemList"] as! [[String: Any]]
@@ -77,6 +77,8 @@ public class ChromeSafariBrowserManager: ChannelDelegate {
flutterViewController.present(safari, animated: true) {
result(true)
}
+
+ ChromeSafariBrowserManager.browsers[id] = safari
}
return
}
diff --git a/ios/Classes/SafariViewController/CustomUIActivity.swift b/ios/Classes/SafariViewController/CustomUIActivity.swift
new file mode 100644
index 00000000..b661a9ba
--- /dev/null
+++ b/ios/Classes/SafariViewController/CustomUIActivity.swift
@@ -0,0 +1,53 @@
+//
+// CustomUIActivity.swift
+// flutter_inappwebview
+//
+// Created by Lorenzo Pichilli on 08/05/22.
+//
+
+import Foundation
+
+class CustomUIActivity : UIActivity {
+ var viewId: String
+ var id: Int64
+ var url: URL
+ var title: String?
+ var type: UIActivity.ActivityType?
+ var label: String?
+ var image: UIImage?
+
+ init(viewId: String, id: Int64, url: URL, title: String?, label: String?, type: UIActivity.ActivityType?, image: UIImage?) {
+ self.viewId = viewId
+ self.id = id
+ self.url = url
+ self.title = title
+ self.label = label
+ self.type = type
+ self.image = image
+ }
+
+ override class var activityCategory: UIActivity.Category {
+ return .action
+ }
+
+ override var activityType: UIActivity.ActivityType? {
+ return type
+ }
+
+ override var activityTitle: String? {
+ return label
+ }
+
+ override var activityImage: UIImage? {
+ return image
+ }
+
+ override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
+ return true
+ }
+
+ override func perform() {
+ let browser = ChromeSafariBrowserManager.browsers[viewId]
+ browser??.channelDelegate?.onChromeSafariBrowserMenuItemActionPerform(id: id, url: url, title: title)
+ }
+}
diff --git a/ios/Classes/SafariViewController/SafariViewController.swift b/ios/Classes/SafariViewController/SafariViewController.swift
index cf8fed37..ee88280d 100755
--- a/ios/Classes/SafariViewController/SafariViewController.swift
+++ b/ios/Classes/SafariViewController/SafariViewController.swift
@@ -39,11 +39,6 @@ public class SafariViewController: SFSafariViewController, SFSafariViewControlle
self.delegate = self
}
- deinit {
- debugPrint("SafariViewController - dealloc")
- dispose()
- }
-
public override func viewWillAppear(_ animated: Bool) {
// prepareSafariBrowser()
super.viewWillAppear(animated)
@@ -127,49 +122,9 @@ public class SafariViewController: SFSafariViewController, SFSafariViewControlle
delegate = nil
ChromeSafariBrowserManager.browsers[id] = nil
}
-}
-
-class CustomUIActivity : UIActivity {
- var viewId: String
- var id: Int64
- var url: URL
- var title: String?
- var type: UIActivity.ActivityType?
- var label: String?
- var image: UIImage?
- init(viewId: String, id: Int64, url: URL, title: String?, label: String?, type: UIActivity.ActivityType?, image: UIImage?) {
- self.viewId = viewId
- self.id = id
- self.url = url
- self.title = title
- self.label = label
- self.type = type
- self.image = image
- }
-
- override class var activityCategory: UIActivity.Category {
- return .action
- }
-
- override var activityType: UIActivity.ActivityType? {
- return type
- }
-
- override var activityTitle: String? {
- return label
- }
-
- override var activityImage: UIImage? {
- return image
- }
-
- override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
- return true
- }
-
- override func perform() {
- let browser = ChromeSafariBrowserManager.browsers[viewId]
- browser??.channelDelegate?.onChromeSafariBrowserMenuItemActionPerform(id: id, url: url, title: title)
+ deinit {
+ debugPrint("SafariViewController - dealloc")
+ dispose()
}
}
diff --git a/ios/Classes/SafariViewController/SafariViewControllerChannelDelegate.swift b/ios/Classes/SafariViewController/SafariViewControllerChannelDelegate.swift
index 2ace93cb..dcccd803 100644
--- a/ios/Classes/SafariViewController/SafariViewControllerChannelDelegate.swift
+++ b/ios/Classes/SafariViewController/SafariViewControllerChannelDelegate.swift
@@ -18,16 +18,16 @@ public class SafariViewControllerChannelDelegate : ChannelDelegate {
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
// let arguments = call.arguments as? NSDictionary
switch call.method {
- case "close":
- if let safariViewController = safariViewController {
- safariViewController.close(result: result)
- } else {
- result(false)
- }
- break
- default:
- result(FlutterMethodNotImplemented)
- break
+ case "close":
+ if let safariViewController = safariViewController {
+ safariViewController.close(result: result)
+ } else {
+ result(false)
+ }
+ break
+ default:
+ result(FlutterMethodNotImplemented)
+ break
}
}
diff --git a/ios/Classes/SwiftFlutterPlugin.swift b/ios/Classes/SwiftFlutterPlugin.swift
index e5fbe685..cb0ca7ac 100755
--- a/ios/Classes/SwiftFlutterPlugin.swift
+++ b/ios/Classes/SwiftFlutterPlugin.swift
@@ -34,6 +34,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
var inAppBrowserManager: InAppBrowserManager?
var headlessInAppWebViewManager: HeadlessInAppWebViewManager?
var chromeSafariBrowserManager: ChromeSafariBrowserManager?
+ var webAuthenticationSessionManager: WebAuthenticationSessionManager?
var webViewControllers: [String: InAppBrowserWebViewController?] = [:]
var safariViewControllers: [String: Any?] = [:]
@@ -56,6 +57,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
if #available(iOS 9.0, *) {
myWebStorageManager = MyWebStorageManager(registrar: registrar)
}
+ webAuthenticationSessionManager = WebAuthenticationSessionManager(registrar: registrar)
}
public static func register(with registrar: FlutterPluginRegistrar) {
@@ -83,5 +85,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
(myWebStorageManager as! MyWebStorageManager?)?.dispose()
myWebStorageManager = nil
}
+ webAuthenticationSessionManager?.dispose()
+ webAuthenticationSessionManager = nil
}
}
diff --git a/ios/Classes/WebAuthenticationSession/WebAuthenticationSession.swift b/ios/Classes/WebAuthenticationSession/WebAuthenticationSession.swift
new file mode 100644
index 00000000..d69ad535
--- /dev/null
+++ b/ios/Classes/WebAuthenticationSession/WebAuthenticationSession.swift
@@ -0,0 +1,106 @@
+//
+// WebAuthenticationSession.swift
+// flutter_inappwebview
+//
+// Created by Lorenzo Pichilli on 08/05/22.
+//
+
+import Foundation
+import AuthenticationServices
+import SafariServices
+
+public class WebAuthenticationSession : NSObject, ASWebAuthenticationPresentationContextProviding, Disposable {
+ static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_webauthenticationsession_"
+ var id: String
+ var url: URL
+ var callbackURLScheme: String?
+ var settings: WebAuthenticationSessionSettings
+ var session: Any?
+ var channelDelegate: WebAuthenticationSessionChannelDelegate?
+ private var _canStart = true
+
+ public init(id: String, url: URL, callbackURLScheme: String?, settings: WebAuthenticationSessionSettings) {
+ self.id = id
+ self.url = url
+ self.settings = settings
+ super.init()
+ self.callbackURLScheme = callbackURLScheme
+ if #available(iOS 12.0, *) {
+ let session = ASWebAuthenticationSession(url: self.url, callbackURLScheme: self.callbackURLScheme, completionHandler: self.completionHandler)
+ if #available(iOS 13.0, *) {
+ session.presentationContextProvider = self
+ }
+ self.session = session
+ } else if #available(iOS 11.0, *) {
+ self.session = SFAuthenticationSession(url: self.url, callbackURLScheme: self.callbackURLScheme, completionHandler: self.completionHandler)
+ }
+ let channel = FlutterMethodChannel(name: WebAuthenticationSession.METHOD_CHANNEL_NAME_PREFIX + id,
+ binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger())
+ self.channelDelegate = WebAuthenticationSessionChannelDelegate(webAuthenticationSession: self, channel: channel)
+ }
+
+ public func prepare() {
+ if #available(iOS 13.0, *), let session = session as? ASWebAuthenticationSession {
+ session.prefersEphemeralWebBrowserSession = settings.prefersEphemeralWebBrowserSession
+ }
+ }
+
+ public func completionHandler(url: URL?, error: Error?) -> Void {
+ channelDelegate?.onComplete(url: url, errorCode: error?._code)
+ }
+
+ public func canStart() -> Bool {
+ guard let session = session else {
+ return false
+ }
+ if #available(iOS 13.4, *), let session = session as? ASWebAuthenticationSession {
+ return session.canStart
+ }
+ return _canStart
+ }
+
+ public func start() -> Bool {
+ guard let session = session else {
+ return false
+ }
+ var started = false
+ if #available(iOS 12.0, *), let session = session as? ASWebAuthenticationSession {
+ started = session.start()
+ } else if #available(iOS 11.0, *), let session = session as? SFAuthenticationSession {
+ started = session.start()
+ }
+ if started {
+ _canStart = false
+ }
+ return started
+ }
+
+ public func cancel() {
+ guard let session = session else {
+ return
+ }
+ if #available(iOS 12.0, *), let session = session as? ASWebAuthenticationSession {
+ session.cancel()
+ } else if #available(iOS 11.0, *), let session = session as? SFAuthenticationSession {
+ session.cancel()
+ }
+ }
+
+ @available(iOS 12.0, *)
+ public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
+ return UIApplication.shared.windows.first { $0.isKeyWindow } ?? ASPresentationAnchor()
+ }
+
+ public func dispose() {
+ cancel()
+ channelDelegate?.dispose()
+ channelDelegate = nil
+ session = nil
+ WebAuthenticationSessionManager.sessions[id] = nil
+ }
+
+ deinit {
+ debugPrint("WebAuthenticationSession - dealloc")
+ dispose()
+ }
+}
diff --git a/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionChannelDelegate.swift b/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionChannelDelegate.swift
new file mode 100644
index 00000000..ef517bc6
--- /dev/null
+++ b/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionChannelDelegate.swift
@@ -0,0 +1,73 @@
+//
+// WebAuthenticationSessionChannelDelegate.swift
+// flutter_inappwebview
+//
+// Created by Lorenzo Pichilli on 08/05/22.
+//
+
+import Foundation
+
+public class WebAuthenticationSessionChannelDelegate : ChannelDelegate {
+ private weak var webAuthenticationSession: WebAuthenticationSession?
+
+ public init(webAuthenticationSession: WebAuthenticationSession, channel: FlutterMethodChannel) {
+ super.init(channel: channel)
+ self.webAuthenticationSession = webAuthenticationSession
+ }
+
+ public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ // let arguments = call.arguments as? NSDictionary
+ switch call.method {
+ case "canStart":
+ if let webAuthenticationSession = webAuthenticationSession {
+ result(webAuthenticationSession.canStart())
+ } else {
+ result(false)
+ }
+ break
+ case "start":
+ if let webAuthenticationSession = webAuthenticationSession {
+ result(webAuthenticationSession.start())
+ } else {
+ result(false)
+ }
+ break
+ case "cancel":
+ if let webAuthenticationSession = webAuthenticationSession {
+ webAuthenticationSession.cancel()
+ result(true)
+ } else {
+ result(false)
+ }
+ break
+ case "dispose":
+ if let webAuthenticationSession = webAuthenticationSession {
+ webAuthenticationSession.dispose()
+ result(true)
+ } else {
+ result(false)
+ }
+ break
+ default:
+ result(FlutterMethodNotImplemented)
+ break
+ }
+ }
+
+ public func onComplete(url: URL?, errorCode: Int?) {
+ let arguments: [String: Any?] = [
+ "url": url?.absoluteString,
+ "errorCode": errorCode
+ ]
+ channel?.invokeMethod("onComplete", arguments: arguments)
+ }
+
+ public override func dispose() {
+ super.dispose()
+ webAuthenticationSession = nil
+ }
+
+ deinit {
+ dispose()
+ }
+}
diff --git a/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift b/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift
new file mode 100644
index 00000000..bfb21b9a
--- /dev/null
+++ b/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift
@@ -0,0 +1,78 @@
+//
+// WebAuthenticationSessionManager.swift
+// flutter_inappwebview
+//
+// Created by Lorenzo Pichilli on 08/05/22.
+//
+
+import Flutter
+import UIKit
+import WebKit
+import Foundation
+import AVFoundation
+import SafariServices
+
+public class WebAuthenticationSessionManager: ChannelDelegate {
+ static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_webauthenticationsession"
+ static var registrar: FlutterPluginRegistrar?
+ static var sessions: [String: WebAuthenticationSession?] = [:]
+
+ init(registrar: FlutterPluginRegistrar) {
+ super.init(channel: FlutterMethodChannel(name: WebAuthenticationSessionManager.METHOD_CHANNEL_NAME, binaryMessenger: registrar.messenger()))
+ WebAuthenticationSessionManager.registrar = registrar
+ }
+
+ public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ let arguments = call.arguments as? NSDictionary
+
+ switch call.method {
+ case "create":
+ let id = arguments!["id"] as! String
+ let url = arguments!["url"] as! String
+ let callbackURLScheme = arguments!["callbackURLScheme"] as? String
+ let initialSettings = arguments!["initialSettings"] as! [String: Any?]
+ create(id: id, url: url, callbackURLScheme: callbackURLScheme, settings: initialSettings, result: result)
+ break
+ case "isAvailable":
+ if #available(iOS 11.0, *) {
+ result(true)
+ } else {
+ result(false)
+ }
+ break
+ default:
+ result(FlutterMethodNotImplemented)
+ break
+ }
+ }
+
+ public func create(id: String, url: String, callbackURLScheme: String?, settings: [String: Any?], result: @escaping FlutterResult) {
+ if #available(iOS 11.0, *) {
+ let sessionUrl = URL(string: url) ?? URL(string: "about:blank")!
+ let initialSettings = WebAuthenticationSessionSettings()
+ let _ = initialSettings.parse(settings: settings)
+ let session = WebAuthenticationSession(id: id, url: sessionUrl, callbackURLScheme: callbackURLScheme, settings: initialSettings)
+ session.prepare()
+ WebAuthenticationSessionManager.sessions[id] = session
+ result(true)
+ return
+ }
+
+ result(FlutterError.init(code: "WebAuthenticationSessionManager", message: "WebAuthenticationSession is not available!", details: nil))
+ }
+
+ public override func dispose() {
+ super.dispose()
+ WebAuthenticationSessionManager.registrar = nil
+ let sessions = WebAuthenticationSessionManager.sessions.values
+ sessions.forEach { (session: WebAuthenticationSession?) in
+ session?.cancel()
+ session?.dispose()
+ }
+ WebAuthenticationSessionManager.sessions.removeAll()
+ }
+
+ deinit {
+ dispose()
+ }
+}
diff --git a/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionSettings.swift b/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionSettings.swift
new file mode 100644
index 00000000..b737381e
--- /dev/null
+++ b/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionSettings.swift
@@ -0,0 +1,30 @@
+//
+// WebAuthenticationSessionSettings.swift
+// flutter_inappwebview
+//
+// Created by Lorenzo Pichilli on 08/05/22.
+//
+
+import Foundation
+import AuthenticationServices
+import SafariServices
+
+@objcMembers
+public class WebAuthenticationSessionSettings: ISettings {
+
+ var prefersEphemeralWebBrowserSession = false
+
+ override init(){
+ super.init()
+ }
+
+ override func getRealSettings(obj: WebAuthenticationSession?) -> [String: Any?] {
+ var realOptions: [String: Any?] = toMap()
+ if #available(iOS 12.0, *), let session = obj?.session as? ASWebAuthenticationSession {
+ if #available(iOS 13.0, *) {
+ realOptions["prefersEphemeralWebBrowserSession"] = session.prefersEphemeralWebBrowserSession
+ }
+ }
+ return realOptions
+ }
+}
diff --git a/lib/src/chrome_safari_browser/chrome_safari_browser.dart b/lib/src/chrome_safari_browser/chrome_safari_browser.dart
index 688b9c5b..2effd347 100755
--- a/lib/src/chrome_safari_browser/chrome_safari_browser.dart
+++ b/lib/src/chrome_safari_browser/chrome_safari_browser.dart
@@ -62,7 +62,7 @@ class ChromeSafariBrowser {
id = IdGenerator.generate();
this._channel =
MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser_$id');
- this._channel.setMethodCallHandler(handleMethod);
+ this._channel.setMethodCallHandler(_handleMethod);
_isOpened = false;
}
@@ -87,7 +87,7 @@ class ChromeSafariBrowser {
}
}
- Future handleMethod(MethodCall call) async {
+ Future _handleMethod(MethodCall call) async {
_debugLog(call.method, call.arguments);
switch (call.method) {
@@ -131,7 +131,9 @@ class ChromeSafariBrowser {
ChromeSafariBrowserSettings? settings}) async {
assert(url.toString().isNotEmpty);
this.throwIsAlreadyOpened(message: 'Cannot open $url!');
- if (!kIsWeb && defaultTargetPlatform == TargetPlatform.iOS) {
+ if (!kIsWeb &&
+ (defaultTargetPlatform == TargetPlatform.iOS ||
+ defaultTargetPlatform == TargetPlatform.macOS)) {
assert(['http', 'https'].contains(url.scheme),
'The specified URL has an unsupported scheme. Only HTTP and HTTPS URLs are supported on iOS.');
}
diff --git a/lib/src/in_app_browser/in_app_browser.dart b/lib/src/in_app_browser/in_app_browser.dart
index 9da0c544..4051a3e7 100755
--- a/lib/src/in_app_browser/in_app_browser.dart
+++ b/lib/src/in_app_browser/in_app_browser.dart
@@ -72,7 +72,6 @@ class InAppBrowser {
///The default value is [WebViewImplementation.NATIVE].
final WebViewImplementation implementation;
- ///
InAppBrowser(
{this.windowId,
this.initialUserScripts,
@@ -80,13 +79,13 @@ class InAppBrowser {
id = IdGenerator.generate();
this._channel =
MethodChannel('com.pichillilorenzo/flutter_inappbrowser_$id');
- this._channel.setMethodCallHandler(handleMethod);
+ this._channel.setMethodCallHandler(_handleMethod);
_isOpened = false;
webViewController = new InAppWebViewController.fromInAppBrowser(
this._channel, this, this.initialUserScripts);
}
- Future handleMethod(MethodCall call) async {
+ Future _handleMethod(MethodCall call) async {
switch (call.method) {
case "onBrowserCreated":
this._isOpened = true;
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 9aac14a6..02aaa295 100644
--- a/lib/src/in_app_webview/headless_in_app_webview.dart
+++ b/lib/src/in_app_webview/headless_in_app_webview.dart
@@ -17,7 +17,7 @@ import '../util.dart';
///Class that represents a WebView in headless mode.
///It can be used to run a WebView in background without attaching an `InAppWebView` to the widget tree.
///
-///Remember to dispose it when you don't need it anymore.
+///**NOTE**: Remember to dispose it when you don't need it anymore.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
@@ -455,11 +455,11 @@ class HeadlessInAppWebView implements WebView {
///Use [onLoadResourceWithCustomScheme] instead.
@Deprecated('Use onLoadResourceWithCustomScheme instead')
@override
- final Future Function(
+ Future Function(
InAppWebViewController controller, Uri url)? onLoadResourceCustomScheme;
@override
- final Future Function(
+ Future Function(
InAppWebViewController controller, WebResourceRequest request)? onLoadResourceWithCustomScheme;
@override
diff --git a/lib/src/main.dart b/lib/src/main.dart
index 3afb37c8..9f53aab5 100644
--- a/lib/src/main.dart
+++ b/lib/src/main.dart
@@ -14,4 +14,5 @@ export 'http_auth_credentials_database.dart';
export 'context_menu.dart';
export 'pull_to_refresh/main.dart';
export 'web_message/main.dart';
+export 'web_authentication_session/main.dart';
export 'debug_logging_settings.dart';
diff --git a/lib/src/pull_to_refresh/pull_to_refresh_controller.dart b/lib/src/pull_to_refresh/pull_to_refresh_controller.dart
index 12e37900..b8f9aeee 100644
--- a/lib/src/pull_to_refresh/pull_to_refresh_controller.dart
+++ b/lib/src/pull_to_refresh/pull_to_refresh_controller.dart
@@ -40,7 +40,7 @@ class PullToRefreshController {
this.settings = settings ?? PullToRefreshSettings();
}
- Future handleMethod(MethodCall call) async {
+ Future _handleMethod(MethodCall call) async {
switch (call.method) {
case "onRefresh":
if (onRefresh != null) onRefresh!();
@@ -166,6 +166,6 @@ class PullToRefreshController {
void initMethodChannel(dynamic id) {
this._channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_$id');
- this._channel?.setMethodCallHandler(handleMethod);
+ this._channel?.setMethodCallHandler(_handleMethod);
}
}
diff --git a/lib/src/types/main.dart b/lib/src/types/main.dart
index 0152a459..73ad6b2e 100644
--- a/lib/src/types/main.dart
+++ b/lib/src/types/main.dart
@@ -148,4 +148,5 @@ export 'media_capture_state.dart';
export 'proxy_rule.dart';
export 'proxy_scheme_filter.dart';
export 'force_dark_strategy.dart';
-export 'url_request_attribution.dart';
\ No newline at end of file
+export 'url_request_attribution.dart';
+export 'web_authentication_session_error.dart';
\ No newline at end of file
diff --git a/lib/src/types/web_authentication_session_error.dart b/lib/src/types/web_authentication_session_error.dart
new file mode 100644
index 00000000..d4220297
--- /dev/null
+++ b/lib/src/types/web_authentication_session_error.dart
@@ -0,0 +1,57 @@
+///Class that represents the error code for a web authentication session error.
+class WebAuthenticationSessionError {
+ final int _value;
+
+ const WebAuthenticationSessionError._internal(this._value);
+
+ ///Set of all values of [WebAuthenticationSessionError].
+ static final Set values = [
+ WebAuthenticationSessionError.CANCELED_LOGIN,
+ WebAuthenticationSessionError.PRESENTATION_CONTEXT_NOT_PROVIDED,
+ WebAuthenticationSessionError.PRESENTATION_CONTEXT_INVALID
+ ].toSet();
+
+ ///Gets a possible [WebAuthenticationSessionError] instance from an [int] value.
+ static WebAuthenticationSessionError? fromValue(int? value) {
+ if (value != null) {
+ try {
+ return WebAuthenticationSessionError.values
+ .firstWhere((element) => element.toValue() == value);
+ } catch (e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ ///Gets [int] value.
+ int toValue() => _value;
+
+ @override
+ String toString() {
+ switch (_value) {
+ case 1:
+ return "CANCELED_LOGIN";
+ case 2:
+ return "PRESENTATION_CONTEXT_NOT_PROVIDED";
+ case 3:
+ return "PRESENTATION_CONTEXT_INVALID";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ ///The login has been canceled.
+ static final CANCELED_LOGIN = WebAuthenticationSessionError._internal(1);
+
+ ///A context wasn’t provided.
+ static final PRESENTATION_CONTEXT_NOT_PROVIDED = WebAuthenticationSessionError._internal(2);
+
+ ///The context was invalid.
+ static final PRESENTATION_CONTEXT_INVALID = WebAuthenticationSessionError._internal(3);
+
+ bool operator ==(value) => value == _value;
+
+ @override
+ int get hashCode => _value.hashCode;
+}
diff --git a/lib/src/web_authentication_session/main.dart b/lib/src/web_authentication_session/main.dart
new file mode 100644
index 00000000..9f942e6e
--- /dev/null
+++ b/lib/src/web_authentication_session/main.dart
@@ -0,0 +1,2 @@
+export 'web_authenticate_session.dart';
+export 'web_authenticate_session_settings.dart';
diff --git a/lib/src/web_authentication_session/web_authenticate_session.dart b/lib/src/web_authentication_session/web_authenticate_session.dart
new file mode 100755
index 00000000..d0f2d422
--- /dev/null
+++ b/lib/src/web_authentication_session/web_authenticate_session.dart
@@ -0,0 +1,193 @@
+import 'dart:async';
+import 'dart:developer' as developer;
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+import '../util.dart';
+import '../debug_logging_settings.dart';
+import '../types/main.dart';
+
+import 'web_authenticate_session_settings.dart';
+
+///A completion handler for the [WebAuthenticationSession].
+typedef WebAuthenticationSessionCompletionHandler = Future Function(Uri? url, WebAuthenticationSessionError? error)?;
+
+///A session that an app uses to authenticate a user through a web service.
+///
+///It is implemented using [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) on iOS 12.0+
+///and [SFAuthenticationSession](https://developer.apple.com/documentation/safariservices/sfauthenticationsession) on iOS 11.0.
+///
+///Use an [WebAuthenticationSession] instance to authenticate a user through a web service, including one run by a third party.
+///Initialize the session with a URL that points to the authentication webpage.
+///A browser loads and displays the page, from which the user can authenticate.
+///In iOS, the browser is a secure, embedded web view.
+///In macOS, the system opens the user’s default browser if it supports web authentication sessions, or Safari otherwise.
+///
+///On completion, the service sends a callback URL to the session with an authentication token, and the session passes this URL back to the app through a completion handler.
+///[WebAuthenticationSession] ensures that only the calling app’s session receives the authentication callback, even when more than one app registers the same callback URL scheme.
+///
+///**NOTE**: Remember to dispose it when you don't need it anymore.
+///
+///**NOTE for iOS**: Available only on iOS 11.0+.
+///
+///**Supported Platforms/Implementations**:
+///- iOS
+class WebAuthenticationSession {
+ ///Debug settings.
+ static DebugLoggingSettings debugLoggingSettings = DebugLoggingSettings();
+
+ ///ID used internally.
+ late final String id;
+
+ ///A URL with the `http` or `https` scheme pointing to the authentication webpage.
+ final Uri url;
+
+ ///The custom URL scheme that the app expects in the callback URL.
+ final String? callbackURLScheme;
+
+ ///Initial settings.
+ late final WebAuthenticationSessionSettings? initialSettings;
+
+ ///A completion handler the session calls when it completes successfully, or when the user cancels the session.
+ WebAuthenticationSessionCompletionHandler onComplete;
+
+ late MethodChannel _channel;
+ static const MethodChannel _sharedChannel = const MethodChannel(
+ 'com.pichillilorenzo/flutter_webauthenticationsession');
+
+ ///Used to create and initialize a session.
+ static Future create(
+ {required Uri url,
+ String? callbackURLScheme,
+ WebAuthenticationSessionCompletionHandler onComplete,
+ WebAuthenticationSessionSettings? initialSettings}) async {
+ var session = WebAuthenticationSession._create(
+ url: url,
+ callbackURLScheme: callbackURLScheme,
+ onComplete: onComplete,
+ initialSettings: initialSettings);
+ initialSettings =
+ session.initialSettings ?? WebAuthenticationSessionSettings();
+ Map args = {};
+ args.putIfAbsent("id", () => session.id);
+ args.putIfAbsent("url", () => session.url.toString());
+ args.putIfAbsent("callbackURLScheme", () => session.callbackURLScheme);
+ args.putIfAbsent("initialSettings", () => initialSettings?.toMap());
+ await _sharedChannel.invokeMethod('create', args);
+ return session;
+ }
+
+ WebAuthenticationSession._create(
+ {required this.url,
+ this.callbackURLScheme,
+ this.onComplete,
+ WebAuthenticationSessionSettings? initialSettings}) {
+ assert(url.toString().isNotEmpty);
+ if (defaultTargetPlatform == TargetPlatform.iOS ||
+ defaultTargetPlatform == TargetPlatform.macOS) {
+ assert(['http', 'https'].contains(url.scheme),
+ 'The specified URL has an unsupported scheme. Only HTTP and HTTPS URLs are supported on iOS.');
+ }
+
+ id = IdGenerator.generate();
+ this.initialSettings =
+ initialSettings ?? WebAuthenticationSessionSettings();
+ this._channel = MethodChannel(
+ 'com.pichillilorenzo/flutter_webauthenticationsession_$id');
+ this._channel.setMethodCallHandler(_handleMethod);
+ }
+
+ _debugLog(String method, dynamic args) {
+ if (WebAuthenticationSession.debugLoggingSettings.enabled) {
+ for (var regExp
+ in WebAuthenticationSession.debugLoggingSettings.excludeFilter) {
+ if (regExp.hasMatch(method)) return;
+ }
+ var maxLogMessageLength =
+ WebAuthenticationSession.debugLoggingSettings.maxLogMessageLength;
+ String message = "WebAuthenticationSession ID " +
+ id +
+ " calling \"" +
+ method.toString() +
+ "\" using " +
+ args.toString();
+ if (maxLogMessageLength >= 0 && message.length > maxLogMessageLength) {
+ message = message.substring(0, maxLogMessageLength) + "...";
+ }
+ developer.log(message, name: this.runtimeType.toString());
+ }
+ }
+
+ Future _handleMethod(MethodCall call) async {
+ _debugLog(call.method, call.arguments);
+
+ switch (call.method) {
+ case "onComplete":
+ String? url = call.arguments["url"];
+ Uri? uri = url != null ? Uri.parse(url) : null;
+ var error = WebAuthenticationSessionError.fromValue(
+ call.arguments["errorCode"]);
+ if (onComplete != null) {
+ onComplete!(uri, error);
+ }
+ break;
+ default:
+ throw UnimplementedError("Unimplemented ${call.method} method");
+ }
+ }
+
+ ///Indicates whether the session can begin.
+ ///
+ ///**Supported Platforms/Implementations**:
+ ///- iOS ([Official API - ASWebAuthenticationSession.canStart](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/3516277-canstart))
+ Future canStart() async {
+ Map args = {};
+ return await _channel.invokeMethod('canStart', args);
+ }
+
+ ///Starts a web authentication session.
+ ///
+ ///Returns a boolean value indicating whether the web authentication session started successfully.
+ ///
+ ///Only call this method once for a given [WebAuthenticationSession] instance after initialization.
+ ///Calling the [start] method on a canceled session results in a failure.
+ ///
+ ///**Supported Platforms/Implementations**:
+ ///- iOS ([Official API - ASWebAuthenticationSession.start](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/2990953-start))
+ Future start() async {
+ Map args = {};
+ return await _channel.invokeMethod('start', args);
+ }
+
+ ///Cancels a web authentication session.
+ ///
+ ///If the session has already presented a view with the authentication webpage, calling this method dismisses that view.
+ ///Calling [cancel] on an already canceled session has no effect.
+ ///
+ ///**Supported Platforms/Implementations**:
+ ///- iOS ([Official API - ASWebAuthenticationSession.cancel](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/2990951-cancel))
+ Future cancel() async {
+ Map args = {};
+ await _channel.invokeMethod("cancel", args);
+ }
+
+ ///Disposes a web authentication session.
+ ///
+ ///**Supported Platforms/Implementations**:
+ ///- iOS
+ Future dispose() async {
+ Map args = {};
+ await _channel.invokeMethod("dispose", args);
+ }
+
+ ///Returns `true` if [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession)
+ ///or [SFAuthenticationSession](https://developer.apple.com/documentation/safariservices/sfauthenticationsession) is available.
+ ///Otherwise returns `false`.
+ ///
+ ///**Supported Platforms/Implementations**:
+ ///- iOS
+ static Future isAvailable() async {
+ Map args = {};
+ return await _sharedChannel.invokeMethod("isAvailable", args);
+ }
+}
diff --git a/lib/src/web_authentication_session/web_authenticate_session_settings.dart b/lib/src/web_authentication_session/web_authenticate_session_settings.dart
new file mode 100755
index 00000000..d416d660
--- /dev/null
+++ b/lib/src/web_authentication_session/web_authenticate_session_settings.dart
@@ -0,0 +1,55 @@
+import 'package:flutter/foundation.dart';
+import 'web_authenticate_session.dart';
+
+///Class that represents the settings that can be used for a [WebAuthenticationSession].
+class WebAuthenticationSessionSettings {
+ ///A Boolean value that indicates whether the session should ask the browser for a private authentication session.
+ ///
+ ///Set [prefersEphemeralWebBrowserSession] to `true` to request that the browser
+ ///doesn’t share cookies or other browsing data between the authentication session and the user’s normal browser session.
+ ///Whether the request is honored depends on the user’s default web browser.
+ ///Safari always honors the request.
+ ///
+ ///The value of this property is `false` by default.
+ ///
+ ///Set this property before you call [WebAuthenticationSession.start]. Otherwise it has no effect.
+ ///
+ ///**NOTE for iOS**: Available only on iOS 13.0+.
+ ///
+ ///**Supported Platforms/Implementations**:
+ ///- iOS
+ bool prefersEphemeralWebBrowserSession;
+
+ WebAuthenticationSessionSettings(
+ {this.prefersEphemeralWebBrowserSession = false});
+
+ Map toMap() {
+ return {
+ "prefersEphemeralWebBrowserSession": prefersEphemeralWebBrowserSession
+ };
+ }
+
+ static WebAuthenticationSessionSettings fromMap(Map map) {
+ WebAuthenticationSessionSettings settings =
+ new WebAuthenticationSessionSettings();
+ if (defaultTargetPlatform == TargetPlatform.iOS ||
+ defaultTargetPlatform == TargetPlatform.macOS) {
+ settings.prefersEphemeralWebBrowserSession =
+ map["prefersEphemeralWebBrowserSession"];
+ }
+ return settings;
+ }
+
+ Map toJson() {
+ return this.toMap();
+ }
+
+ @override
+ String toString() {
+ return toMap().toString();
+ }
+
+ WebAuthenticationSessionSettings copy() {
+ return WebAuthenticationSessionSettings.fromMap(this.toMap());
+ }
+}
diff --git a/lib/src/web_message/web_message_channel.dart b/lib/src/web_message/web_message_channel.dart
index 0a005626..d9e5d6a4 100644
--- a/lib/src/web_message/web_message_channel.dart
+++ b/lib/src/web_message/web_message_channel.dart
@@ -19,7 +19,7 @@ class 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);
+ this._channel.setMethodCallHandler(_handleMethod);
}
static WebMessageChannel? fromMap(Map? map) {
@@ -35,7 +35,7 @@ class WebMessageChannel {
return webMessageChannel;
}
- Future handleMethod(MethodCall call) async {
+ Future _handleMethod(MethodCall call) async {
switch (call.method) {
case "onMessage":
int index = call.arguments["index"];
diff --git a/lib/src/web_message/web_message_listener.dart b/lib/src/web_message/web_message_listener.dart
index e72ace03..9bb2cff8 100644
--- a/lib/src/web_message/web_message_listener.dart
+++ b/lib/src/web_message/web_message_listener.dart
@@ -36,10 +36,10 @@ class WebMessageListener {
"allowedOriginRules cannot contain empty strings");
this._channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_web_message_listener_$jsObjectName');
- this._channel.setMethodCallHandler(handleMethod);
+ this._channel.setMethodCallHandler(_handleMethod);
}
- Future handleMethod(MethodCall call) async {
+ Future _handleMethod(MethodCall call) async {
switch (call.method) {
case "onPostMessage":
if (_replyProxy == null) {