Skip to content

Commit

Permalink
Support UIImplementation #160
Browse files Browse the repository at this point in the history
ref #159
  • Loading branch information
tung2744 authored Feb 20, 2024
2 parents 9094d61 + f6a43c3 commit ee76ee5
Show file tree
Hide file tree
Showing 14 changed files with 351 additions and 169 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:

jobs:
gh-pages:
runs-on: macos-12
runs-on: macos-13
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
Expand All @@ -22,7 +22,7 @@ jobs:
# Latest compatiable version with our codebase is 0.48.0
- run: brew update
- run: brew upgrade swiftformat || brew install swiftformat
- run: sudo xcode-select -s /Applications/Xcode_14.2.app/Contents/Developer
- run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
- run: make lint
- run: pod install --project-directory=example
- run: xcodebuild -showsdks
Expand All @@ -43,7 +43,7 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: "18.x"
- run: sudo xcode-select -s /Applications/Xcode_14.3.1.app/Contents/Developer
- run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
- run: npm install -g appcenter-cli
- working-directory: ./example
run: pod install
Expand Down
32 changes: 22 additions & 10 deletions Authgear.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
/* Begin PBXBuildFile section */
775FF72A2603649500B81ECD /* Biometric.swift in Sources */ = {isa = PBXBuildFile; fileRef = 775FF7292603649500B81ECD /* Biometric.swift */; };
775FF73426047C7700B81ECD /* DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 775FF73326047C7700B81ECD /* DeviceInfo.swift */; };
77EBB6182B833BC600EEDFD2 /* ASWebAuthenticationSessionUIImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77EBB6172B833BC600EEDFD2 /* ASWebAuthenticationSessionUIImplementation.swift */; };
77EBB61C2B833C5F00EEDFD2 /* AGWKWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77EBB61B2B833C5F00EEDFD2 /* AGWKWebViewController.swift */; };
77EBB61E2B833CAA00EEDFD2 /* WKWebViewUIImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77EBB61D2B833CAA00EEDFD2 /* WKWebViewUIImplementation.swift */; };
95E67335299F652100C9466C /* AuthgearExperimental.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95E67332299F652000C9466C /* AuthgearExperimental.swift */; };
A652CEAE2A812CA9006E789F /* App2AppOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A652CEAD2A812CA9006E789F /* App2AppOptions.swift */; };
A652CEB32A8131BF006E789F /* App2App.swift in Sources */ = {isa = PBXBuildFile; fileRef = A652CEB22A8131BF006E789F /* App2App.swift */; };
Expand All @@ -27,7 +30,7 @@
F83D84AA24F66C5100B4393C /* OIDCConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83D84A924F66C5100B4393C /* OIDCConfiguration.swift */; };
F83D84AC24F66D3400B4393C /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83D84AB24F66D3400B4393C /* APIClient.swift */; };
F83D84AE24F6A93600B4393C /* CodeVerifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83D84AD24F6A93600B4393C /* CodeVerifier.swift */; };
F83D84B024F860AA00B4393C /* AuthenticationSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83D84AF24F860AA00B4393C /* AuthenticationSession.swift */; };
F83D84B024F860AA00B4393C /* UIImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83D84AF24F860AA00B4393C /* UIImplementation.swift */; };
F8A657A32501845D0027888F /* Decodable+Any.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A657A22501845D0027888F /* Decodable+Any.swift */; };
F8A657A52501D31E0027888F /* JWK.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A657A42501D31E0027888F /* JWK.swift */; };
F8A657A7250551ED0027888F /* JWT.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A657A6250551ED0027888F /* JWT.swift */; };
Expand Down Expand Up @@ -57,6 +60,9 @@
/* Begin PBXFileReference section */
775FF7292603649500B81ECD /* Biometric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Biometric.swift; sourceTree = "<group>"; };
775FF73326047C7700B81ECD /* DeviceInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceInfo.swift; sourceTree = "<group>"; };
77EBB6172B833BC600EEDFD2 /* ASWebAuthenticationSessionUIImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASWebAuthenticationSessionUIImplementation.swift; sourceTree = "<group>"; };
77EBB61B2B833C5F00EEDFD2 /* AGWKWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AGWKWebViewController.swift; sourceTree = "<group>"; };
77EBB61D2B833CAA00EEDFD2 /* WKWebViewUIImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKWebViewUIImplementation.swift; sourceTree = "<group>"; };
95E67332299F652000C9466C /* AuthgearExperimental.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthgearExperimental.swift; sourceTree = "<group>"; };
A652CEAD2A812CA9006E789F /* App2AppOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App2AppOptions.swift; sourceTree = "<group>"; };
A652CEB22A8131BF006E789F /* App2App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App2App.swift; sourceTree = "<group>"; };
Expand All @@ -78,7 +84,7 @@
F83D84A924F66C5100B4393C /* OIDCConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCConfiguration.swift; sourceTree = "<group>"; };
F83D84AB24F66D3400B4393C /* APIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIClient.swift; sourceTree = "<group>"; };
F83D84AD24F6A93600B4393C /* CodeVerifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeVerifier.swift; sourceTree = "<group>"; };
F83D84AF24F860AA00B4393C /* AuthenticationSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationSession.swift; sourceTree = "<group>"; };
F83D84AF24F860AA00B4393C /* UIImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImplementation.swift; sourceTree = "<group>"; };
F8A657A22501845D0027888F /* Decodable+Any.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decodable+Any.swift"; sourceTree = "<group>"; };
F8A657A42501D31E0027888F /* JWK.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWK.swift; sourceTree = "<group>"; };
F8A657A6250551ED0027888F /* JWT.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWT.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -165,7 +171,10 @@
F83D84AB24F66D3400B4393C /* APIClient.swift */,
F83D84A724F64D0E00B4393C /* Authgear.swift */,
95E67332299F652000C9466C /* AuthgearExperimental.swift */,
F83D84AF24F860AA00B4393C /* AuthenticationSession.swift */,
F83D84AF24F860AA00B4393C /* UIImplementation.swift */,
77EBB6172B833BC600EEDFD2 /* ASWebAuthenticationSessionUIImplementation.swift */,
77EBB61B2B833C5F00EEDFD2 /* AGWKWebViewController.swift */,
77EBB61D2B833CAA00EEDFD2 /* WKWebViewUIImplementation.swift */,
F83D84A924F66C5100B4393C /* OIDCConfiguration.swift */,
F83D84AD24F6A93600B4393C /* CodeVerifier.swift */,
F83392EE24FC344D00A69D3C /* Storage.swift */,
Expand Down Expand Up @@ -293,17 +302,20 @@
A652CEFA2A8A2C8F006E789F /* Asn1IntegerConversion.swift in Sources */,
F8A657A925055A7C0027888F /* Base64.swift in Sources */,
F83392F324FE38FE00A69D3C /* URLComponents+QueryParames.swift in Sources */,
77EBB6182B833BC600EEDFD2 /* ASWebAuthenticationSessionUIImplementation.swift in Sources */,
A6FCB0A52AF37E4300DAC260 /* URL+origin.swift in Sources */,
775FF72A2603649500B81ECD /* Biometric.swift in Sources */,
775FF73426047C7700B81ECD /* DeviceInfo.swift in Sources */,
77EBB61C2B833C5F00EEDFD2 /* AGWKWebViewController.swift in Sources */,
F8A657A7250551ED0027888F /* JWT.swift in Sources */,
A68DAD2229AF686000487DAF /* UILocales.swift in Sources */,
F8A657A52501D31E0027888F /* JWK.swift in Sources */,
F8A657A32501845D0027888F /* Decodable+Any.swift in Sources */,
A652CEB72A822753006E789F /* App2AppAuthenticationOptions.swift in Sources */,
A652CEB92A8227C2006E789F /* App2AppAuthenticateRequest.swift in Sources */,
F83D84AC24F66D3400B4393C /* APIClient.swift in Sources */,
F83D84B024F860AA00B4393C /* AuthenticationSession.swift in Sources */,
77EBB61E2B833CAA00EEDFD2 /* WKWebViewUIImplementation.swift in Sources */,
F83D84B024F860AA00B4393C /* UIImplementation.swift in Sources */,
F83D84A824F64D0E00B4393C /* Authgear.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -338,7 +350,7 @@
);
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = Authgear.xcodeproj/Authgear_Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
MACOSX_DEPLOYMENT_TARGET = 10.10;
OTHER_CFLAGS = "$(inherited)";
Expand Down Expand Up @@ -368,7 +380,7 @@
);
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = Authgear.xcodeproj/Authgear_Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
MACOSX_DEPLOYMENT_TARGET = 10.10;
OTHER_CFLAGS = "$(inherited)";
Expand Down Expand Up @@ -403,7 +415,7 @@
"SWIFT_PACKAGE=1",
"DEBUG=1",
);
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MACOSX_DEPLOYMENT_TARGET = 10.10;
ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -DXcode";
Expand All @@ -427,7 +439,7 @@
);
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = Authgear.xcodeproj/AuthgearTests_Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.10;
OTHER_CFLAGS = "$(inherited)";
Expand All @@ -453,7 +465,7 @@
);
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = Authgear.xcodeproj/AuthgearTests_Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.10;
OTHER_CFLAGS = "$(inherited)";
Expand Down Expand Up @@ -481,7 +493,7 @@
"$(inherited)",
"SWIFT_PACKAGE=1",
);
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MACOSX_DEPLOYMENT_TARGET = 10.10;
OTHER_SWIFT_FLAGS = "$(inherited) -DXcode";
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# The installed SDKs are listed here.
# Note that this also depends on the runner image.
# macos-12 is now being used.
# https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md
DEVICE_SDK=iphoneos16.2
SIMULATOR_SDK=iphonesimulator16.2
TEST_DESTINATION="platform=iOS Simulator,name=iPhone 14,OS=16.2"
# macos-13 is now being used.
# https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md
DEVICE_SDK=iphoneos17.2
SIMULATOR_SDK=iphonesimulator17.2
TEST_DESTINATION="platform=iOS Simulator,name=iPhone 15,OS=17.2"

GIT_HASH ?= git-$(shell git rev-parse --short=12 HEAD)

Expand Down
180 changes: 180 additions & 0 deletions Sources/AGWKWebViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import Foundation
import UIKit
import WebKit

let AGWKWebViewControllerErrorDomain: String = "AGWKWebViewController"
let AGWKWebViewControllerErrorCodeCanceledLogin: Int = 1

protocol AGWKWebViewControllerPresentationContextProviding: AnyObject {
func presentationAnchor(for: AGWKWebViewController) -> UIWindow
}

class AGWKWebViewController: UIViewController, WKNavigationDelegate {
typealias CompletionHandler = (URL?, Error?) -> Void
weak var presentationContextProvider: AGWKWebViewControllerPresentationContextProviding?
var navigationBarBackgroundColor: UIColor?
var navigationBarButtonTintColor: UIColor?

private let url: URL
private let redirectURI: URL
private var completionHandler: CompletionHandler?
private let webView: WKWebView
private var result: URL?

init(url: URL, redirectURI: URL, completionHandler: @escaping CompletionHandler) {
self.url = url
self.redirectURI = redirectURI
self.completionHandler = completionHandler

let configuration = WKWebViewConfiguration()
self.webView = WKWebView(frame: .zero, configuration: configuration)
self.webView.translatesAutoresizingMaskIntoConstraints = false
self.webView.allowsBackForwardNavigationGestures = true

super.init(nibName: nil, bundle: nil)

self.webView.navigationDelegate = self
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()

// Configure layout
self.view.addSubview(self.webView)
if #available(iOS 11.0, *) {
// Extend the web view to the top edge of the screen.
// WKWebView magically offset the content so that the content is not covered by the navigation bar initially.
self.webView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.webView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor).isActive = true
self.webView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor).isActive = true
// Extend the web view to the bottom edge of the screen.
self.webView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
}

// Configure the bounce behavior
self.webView.scrollView.bounces = false
self.webView.scrollView.alwaysBounceVertical = false
self.webView.scrollView.alwaysBounceHorizontal = false

// Configure navigation bar appearance
if #available(iOS 13.0, *) {
let appearance = UINavigationBarAppearance()
appearance.configureWithTransparentBackground()
if let navigationBarBackgroundColor = self.navigationBarBackgroundColor {
appearance.backgroundColor = navigationBarBackgroundColor
}
self.navigationItem.standardAppearance = appearance
self.navigationItem.compactAppearance = appearance
self.navigationItem.scrollEdgeAppearance = appearance
if #available(iOS 15.0, *) {
self.navigationItem.compactScrollEdgeAppearance = appearance
}
}

// Configure back button
self.navigationItem.hidesBackButton = true
var backButton: UIBarButtonItem
if #available(iOS 13.0, *) {
let backButtonImage = UIImage(systemName: "chevron.backward")
backButton = UIBarButtonItem(image: backButtonImage, style: .plain, target: self, action: #selector(onTapBackButton))
} else {
backButton = UIBarButtonItem(title: "<", style: .plain, target: self, action: #selector(onTapBackButton))
}
if let navigationBarButtonTintColor = self.navigationBarButtonTintColor {
backButton.tintColor = navigationBarButtonTintColor
}
self.navigationItem.leftBarButtonItem = backButton

// Configure close button
var closeButton: UIBarButtonItem
if #available(iOS 13.0, *) {
let closeButtonImage = UIImage(systemName: "xmark")
closeButton = UIBarButtonItem(image: closeButtonImage, style: .plain, target: self, action: #selector(onTapCloseButton))
} else {
closeButton = UIBarButtonItem(title: "X", style: .plain, target: self, action: #selector(onTapCloseButton))
}
if let navigationBarButtonTintColor = self.navigationBarButtonTintColor {
closeButton.tintColor = navigationBarButtonTintColor
}
self.navigationItem.rightBarButtonItem = closeButton

let request = URLRequest(url: self.url)
self.webView.load(request)
}

override func viewDidDisappear(_ animated: Bool) {
// We only call completion handler here because
// The view controller could be swiped to dismiss.
// viewDidDisappear is the most rebust way to detect whether the view controller is dismissed.
if let result = self.result {
self.completionHandler?(result, nil)
} else {
let err = NSError(domain: AGWKWebViewControllerErrorDomain, code: AGWKWebViewControllerErrorCodeCanceledLogin)
self.completionHandler?(nil, err)
}
self.completionHandler = nil
}

@objc private func onTapBackButton() {
if (self.webView.canGoBack) {
_ = self.webView.goBack()
} else {
self.cancel()
}
}

@objc private func onTapCloseButton() {
self.cancel()
}

func cancel() {
self.dismissSelf()
}

func start() {
if let presentationAnchor = self.presentationContextProvider?.presentationAnchor(for: self) {
let navigationController = UINavigationController(rootViewController: self)
// Use the configured modal presentation style.
navigationController.modalPresentationStyle = self.modalPresentationStyle
presentationAnchor.rootViewController?.present(navigationController, animated: true)
}
}

private func dismissSelf() {
self.navigationController?.presentingViewController?.dismiss(animated: true)
}

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let navigationURL = navigationAction.request.url {
var parts = URLComponents(url: navigationURL, resolvingAgainstBaseURL: false)
parts?.query = nil
parts?.fragment = nil
if let partsString = parts?.string {
if partsString == self.redirectURI.absoluteString {
decisionHandler(.cancel)
self.result = navigationURL
self.dismissSelf()
return
}
}
}

if #available(iOS 14.5, *) {
if navigationAction.shouldPerformDownload {
decisionHandler(.download)
return
} else {
decisionHandler(.allow)
return
}
} else {
decisionHandler(.allow)
return
}
}
}
23 changes: 23 additions & 0 deletions Sources/ASWebAuthenticationSessionUIImplementation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import AuthenticationServices

public class ASWebAuthenticationSessionUIImplementation: NSObject, UIImplementation, ASWebAuthenticationPresentationContextProviding {
public func openAuthorizationURL(url: URL, redirectURI: URL, shareCookiesWithDeviceBrowser: Bool, completion: @escaping CompletionHandler) {
let session = ASWebAuthenticationSession(url: url, callbackURLScheme: redirectURI.scheme!) { url, error in
if let error = error {
completion(.failure(wrapError(error: error)))
}
if let url = url {
completion(.success(url))
}
}
if #available(iOS 13.0, *) {
session.prefersEphemeralWebBrowserSession = !shareCookiesWithDeviceBrowser
session.presentationContextProvider = self
}
session.start()
}

public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
UIApplication.shared.windows.filter { $0.isKeyWindow }.first!
}
}
Loading

0 comments on commit ee76ee5

Please sign in to comment.