Added WebAuthenticationSession for iOS

This commit is contained in:
Lorenzo Pichilli 2022-05-09 01:51:21 +02:00
parent 26ddf0cef9
commit 0a16e1babe
26 changed files with 827 additions and 82 deletions

View File

@ -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

15
example/assets/web-auth.html Executable file
View File

@ -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>

View File

@ -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")),

View File

@ -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(),
});
}
}

View File

@ -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")),
)
]),
));
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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.');
}

View File

@ -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;

View File

@ -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

View File

@ -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';

View File

@ -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);
}
}

View File

@ -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';
export 'url_request_attribution.dart';
export 'web_authentication_session_error.dart';

View File

@ -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 wasnt 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;
}

View File

@ -0,0 +1,2 @@
export 'web_authenticate_session.dart';
export 'web_authenticate_session_settings.dart';

View File

@ -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 users 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 apps 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);
}
}

View File

@ -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
///doesnt share cookies or other browsing data between the authentication session and the users normal browser session.
///Whether the request is honored depends on the users 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());
}
}

View File

@ -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"];

View File

@ -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) {