Делегат WKWebView не отвечает на вызов метода делегата - PullRequest
0 голосов
/ 06 января 2019

У меня есть WKWebView, который используется для представления экрана входа в систему моего провайдера идентификации OAuth.

import UIKit
import WebKit

protocol OAuth2WKWebViewDelegate: class {
    func didReceiveAuthorizationCode(_ code: String) -> Void
    func didRevokeSession() -> Void
}

class OAuth2WKWebViewController: UIViewController {
    let targetUrl: URLComponents
    let webView = WKWebView()

    weak var delegate: OAuth2WKWebViewDelegate?

    init(targetUrl: URLComponents) {
        self.targetUrl = targetUrl
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        loadUrl()
    }
}

extension OAuth2WKWebViewController: WKNavigationDelegate {
    func loadUrl() {
        guard let url = targetUrl.url else { return }

        view = webView
        webView.load(URLRequest(url: url))
        webView.allowsBackForwardNavigationGestures = true
        webView.navigationDelegate = self
    }

    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        if let url = navigationAction.request.url {
            if url.scheme == "appdev", url.absoluteString.range(of: "code") != nil {
                let urlParts = url.absoluteString.components(separatedBy: "?")
                let code = urlParts[1].components(separatedBy: "code=")[1]
                delegate?.didReceiveAuthorizationCode(code)
            }

            if url.absoluteString == "appdev://oauth-callback-after-sign-out" {
                delegate?.didRevokeSession()
            }
        }
        decisionHandler(.allow)
    }
}

У меня также есть IdentityService, который я использую для представления этого представления и ответа на его успех / ошибку.

protocol IdentityServiceProtocol {
    var hasValidToken: Bool { get }
    func initAuthCodeFlow() -> Void
    func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void
    func storeOAuthTokens(accessToken: String, refreshToken: String, completion: @escaping () -> Void) -> Void
    func renderAuthView() -> Void
}

class IdentityService: IdentityServiceProtocol {

    fileprivate var apiClient: APIClient
    fileprivate var keyChainService: KeyChainService

    init(apiClient: APIClient = APIClient(), keyChainService: KeyChainService = KeyChainService()) {
        self.apiClient = apiClient
        self.keyChainService = keyChainService
    }

    var hasValidToken: Bool {
        return keyChainService.fetchSingleObject(withKey: "AccessToken") != nil
    }

    func initAuthCodeFlow() -> Void {
        let queryItems = ["response_type": "code", "client_id": clientId, "redirect_uri": redirectUri, "state": state, "scope": scope]
        renderOAuthWebView(forService: .auth(company: "benefex"), queryitems: queryItems)
    }

    func initRevokeSession() -> Void {
        guard let refreshToken = keyChainService.fetchSingleObject(withKey: "RefreshToken") else { return }
        let queryItems = ["refresh_token": refreshToken]
        renderOAuthWebView(forService: .revokeSession(company: "benefex"), queryitems: queryItems)
    }

    func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void {
        guard let targetUrl = constructURLComponents(endPoint: service, queryItems: queryitems) else { return }
        let webView = OAuth2WKWebViewController(targetUrl: targetUrl)
        webView.delegate = self
        UIApplication.shared.windows.first?.rootViewController = webView
    }

    func storeOAuthTokens(accessToken: String, refreshToken: String, completion: @escaping ()-> Void) -> Void {
        let success = keyChainService.storeManyObjects(["AccessToken": accessToken, "RefreshToken": refreshToken])
        guard success == true else { return }
        completion()
    }

    func renderAuthView() -> Void {
        UIApplication.shared.windows.first?.rootViewController = UINavigationController.init(rootViewController: AuthenticatedViewController())
    }
}

extension IdentityService: OAuth2WKWebViewDelegate {
    func didReceiveAuthorizationCode(_ code: String) {
        apiClient.call(endpoint: IdentityEndpoint.accessToken(company: "benefex", code: code)) { [weak self] (response: OAuthTokenResponse) in
            switch response {
            case .success(let payload):
                guard let accessToken = payload.accessToken, let refreshToken = payload.refreshToken else { return }
                self?.storeOAuthTokens(accessToken: accessToken, refreshToken: refreshToken) { self?.renderAuthView() }
            case .error:
                // login failed for some reason
                print("could not complete request for access token")
            }
        }
    }

    func didRevokeSession() {
        print("This was called")
    }
}

extension IdentityService {
    fileprivate var state: String {
        return generateState(withLength: 20)
    }

    fileprivate func constructURLComponents(endPoint: IdentityEndpoint, queryItems: [String: String]) -> URLComponents? {
        var url = URLComponents(url: endPoint.baseUrl, resolvingAgainstBaseURL: false)
        url?.path = endPoint.path
        url?.queryItems = queryItems.map { URLQueryItem(name: $0.key, value: $0.value) }

        return url
    }

    fileprivate func generateState(withLength len: Int) -> String {
        let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        let length = UInt32(letters.count)

        var randomString = ""
        for _ in 0..<len {
            let rand = arc4random_uniform(length)
            let idx = letters.index(letters.startIndex, offsetBy: Int(rand))
            let letter = letters[idx]
            randomString += String(letter)
        }
        return randomString
    }
}

extension IdentityService {
    var clientId: String {
        let envVar = ProcessInfo.processInfo.environment
        guard let value = envVar["APP_CLIENT_ID"] else { fatalError("Missing APP_CLIENT_ID enviroment variable") }
        return value
    }

    var redirectUri: String {
        let envVar = ProcessInfo.processInfo.environment
        guard let value = envVar["APP_REDIRECT_URI"] else { fatalError("Missing APP_REDIRECT_URI enviroment variable") }
        return value
    }

    var scope: String {
        let envVar = ProcessInfo.processInfo.environment
        guard let value = envVar["APP_SCOPES"] else { fatalError("Missing APP_SCOPES enviroment variable") }
        return value
    }
}

delegate?.didReceiveAuthorizationCode(code) работает.

Однако, когда delegate?.didRevokeSession() вызывается из WebView, служба идентификации не отвечает.

Я добавил несколько консольных журналов и вижу, что мой IdentityService деинициализируется при вызове метода выхода из системы.

Я полагаю, что это приводит к тому, что он ничего не делает при срабатывании метода делегата.

Как я могу гарантировать, что метод делегата вызывается по-прежнему?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...