Added WebAuthenticationSession for iOS
This commit is contained in:
parent
26ddf0cef9
commit
0a16e1babe
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Web Auth</title>
|
||||
</head>
|
||||
<body>
|
||||
<form id="form" method="GET" action="test://callback-url">
|
||||
<input type="text" name="token" placeholder="token example">
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
|
@ -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")),
|
||||
|
|
|
@ -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<MyApp> {
|
|||
'/InAppBrowser': (context) => InAppBrowserExampleScreen(),
|
||||
'/ChromeSafariBrowser': (context) => ChromeSafariBrowserExampleScreen(),
|
||||
'/HeadlessInAppWebView': (context) => HeadlessInAppWebViewExampleScreen(),
|
||||
'/WebAuthenticationSession': (context) => WebAuthenticationSessionExampleScreen(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<WebAuthenticationSessionExampleScreen> {
|
||||
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: <Widget>[
|
||||
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")),
|
||||
)
|
||||
]),
|
||||
));
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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<WebAuthenticationSession> {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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<dynamic> handleMethod(MethodCall call) async {
|
||||
Future<dynamic> _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.');
|
||||
}
|
||||
|
|
|
@ -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<dynamic> handleMethod(MethodCall call) async {
|
||||
Future<dynamic> _handleMethod(MethodCall call) async {
|
||||
switch (call.method) {
|
||||
case "onBrowserCreated":
|
||||
this._isOpened = true;
|
||||
|
|
|
@ -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<CustomSchemeResponse?> Function(
|
||||
Future<CustomSchemeResponse?> Function(
|
||||
InAppWebViewController controller, Uri url)? onLoadResourceCustomScheme;
|
||||
|
||||
@override
|
||||
final Future<CustomSchemeResponse?> Function(
|
||||
Future<CustomSchemeResponse?> Function(
|
||||
InAppWebViewController controller, WebResourceRequest request)? onLoadResourceWithCustomScheme;
|
||||
|
||||
@override
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -40,7 +40,7 @@ class PullToRefreshController {
|
|||
this.settings = settings ?? PullToRefreshSettings();
|
||||
}
|
||||
|
||||
Future<dynamic> handleMethod(MethodCall call) async {
|
||||
Future<dynamic> _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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -149,3 +149,4 @@ export 'proxy_rule.dart';
|
|||
export 'proxy_scheme_filter.dart';
|
||||
export 'force_dark_strategy.dart';
|
||||
export 'url_request_attribution.dart';
|
||||
export '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<WebAuthenticationSessionError> 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;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export 'web_authenticate_session.dart';
|
||||
export 'web_authenticate_session_settings.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<void> 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<WebAuthenticationSession> 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<String, dynamic> args = <String, dynamic>{};
|
||||
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<dynamic> _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<bool> canStart() async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
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<bool> start() async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
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<void> cancel() async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
await _channel.invokeMethod("cancel", args);
|
||||
}
|
||||
|
||||
///Disposes a web authentication session.
|
||||
///
|
||||
///**Supported Platforms/Implementations**:
|
||||
///- iOS
|
||||
Future<void> dispose() async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
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<bool> isAvailable() async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
return await _sharedChannel.invokeMethod("isAvailable", args);
|
||||
}
|
||||
}
|
|
@ -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<String, dynamic> toMap() {
|
||||
return {
|
||||
"prefersEphemeralWebBrowserSession": prefersEphemeralWebBrowserSession
|
||||
};
|
||||
}
|
||||
|
||||
static WebAuthenticationSessionSettings fromMap(Map<String, dynamic> map) {
|
||||
WebAuthenticationSessionSettings settings =
|
||||
new WebAuthenticationSessionSettings();
|
||||
if (defaultTargetPlatform == TargetPlatform.iOS ||
|
||||
defaultTargetPlatform == TargetPlatform.macOS) {
|
||||
settings.prefersEphemeralWebBrowserSession =
|
||||
map["prefersEphemeralWebBrowserSession"];
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return this.toMap();
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return toMap().toString();
|
||||
}
|
||||
|
||||
WebAuthenticationSessionSettings copy() {
|
||||
return WebAuthenticationSessionSettings.fromMap(this.toMap());
|
||||
}
|
||||
}
|
|
@ -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<String, dynamic>? map) {
|
||||
|
@ -35,7 +35,7 @@ class WebMessageChannel {
|
|||
return webMessageChannel;
|
||||
}
|
||||
|
||||
Future<dynamic> handleMethod(MethodCall call) async {
|
||||
Future<dynamic> _handleMethod(MethodCall call) async {
|
||||
switch (call.method) {
|
||||
case "onMessage":
|
||||
int index = call.arguments["index"];
|
||||
|
|
|
@ -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<dynamic> handleMethod(MethodCall call) async {
|
||||
Future<dynamic> _handleMethod(MethodCall call) async {
|
||||
switch (call.method) {
|
||||
case "onPostMessage":
|
||||
if (_replyProxy == null) {
|
||||
|
|
Loading…
Reference in New Issue