fix: code review notes
This commit is contained in:
parent
9d3bbc9c71
commit
b98678b235
|
|
@ -24,13 +24,11 @@ import TILogging
|
|||
|
||||
open class BaseWebViewErrorHandler {
|
||||
|
||||
public var logger: TILogger?
|
||||
public init() {
|
||||
|
||||
public init(logger: TILogger? = nil) {
|
||||
self.logger = logger
|
||||
}
|
||||
|
||||
open func didRecievedError(_ error: WebViewErrorModel) {
|
||||
logger?.error("%@", "\(error)")
|
||||
open func didRecievedError(_ error: WebViewError) {
|
||||
// override in subviews
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,17 +22,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public struct WebViewErrorModel {
|
||||
public let url: URL?
|
||||
public let error: Error?
|
||||
public let jsErrorMessage: String?
|
||||
|
||||
public init(url: URL? = nil,
|
||||
error: Error? = nil,
|
||||
jsErrorMessage: String? = nil) {
|
||||
|
||||
self.url = url
|
||||
self.error = error
|
||||
self.jsErrorMessage = jsErrorMessage
|
||||
}
|
||||
public enum WebViewError: Error {
|
||||
case standardError(URL?, Error)
|
||||
case jsError(URL?, String)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,16 +25,14 @@ import enum WebKit.WKNavigationActionPolicy
|
|||
|
||||
open class BaseWebViewNavigator {
|
||||
|
||||
public typealias WebViewNavigationMap = [WebViewUrlComparator: NavigationResult]
|
||||
public let navigationMap: [NavigationPolicy]
|
||||
|
||||
public let navigationMap: WebViewNavigationMap
|
||||
|
||||
public init(navigationMap: WebViewNavigationMap) {
|
||||
public init(navigationMap: [NavigationPolicy]) {
|
||||
self.navigationMap = navigationMap
|
||||
}
|
||||
|
||||
public convenience init() {
|
||||
self.init(navigationMap: [:])
|
||||
self.init(navigationMap: [])
|
||||
}
|
||||
|
||||
open func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy {
|
||||
|
|
@ -42,19 +40,7 @@ open class BaseWebViewNavigator {
|
|||
return .cancel
|
||||
}
|
||||
|
||||
let decision = navigationMap.first { (comparator, _) in
|
||||
url.compare(by: comparator)
|
||||
}?.value
|
||||
|
||||
switch decision {
|
||||
case let .closure(closure):
|
||||
return closure(url)
|
||||
|
||||
case let .simpleResult(result):
|
||||
return result
|
||||
|
||||
case .none:
|
||||
return .cancel
|
||||
}
|
||||
let allowPolicy = navigationMap.filter { $0.policy(for: url) == .allow }
|
||||
return allowPolicy.isEmpty ? .cancel : .allow
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// Copyright (c) 2022 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension URL {
|
||||
func validate(with regex: NSRegularExpression) -> Bool {
|
||||
let range = NSRange(location: 0, length: absoluteString.utf16.count)
|
||||
|
||||
return regex.firstMatch(in: absoluteString, range: range) != nil
|
||||
}
|
||||
|
||||
func validate(by component: URLComponent) -> Bool {
|
||||
switch component {
|
||||
case let .host(host):
|
||||
return self.host == host
|
||||
|
||||
case let .absolutePath(path):
|
||||
return absoluteString == path
|
||||
|
||||
case let .query(query):
|
||||
return (self.query ?? "").contains(query)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// Copyright (c) 2022 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
public enum URLComponent {
|
||||
case host(String)
|
||||
case absolutePath(String)
|
||||
case query(String)
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// Copyright (c) 2022 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import enum WebKit.WKNavigationActionPolicy
|
||||
|
||||
open class AnyNavigationPolicy: NavigationPolicy {
|
||||
|
||||
public init() {
|
||||
|
||||
}
|
||||
|
||||
open func policy(for url: URL) -> WKNavigationActionPolicy {
|
||||
.allow
|
||||
}
|
||||
}
|
||||
|
|
@ -21,10 +21,8 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import TISwiftUtils
|
||||
import enum WebKit.WKNavigationActionPolicy
|
||||
|
||||
public enum NavigationResult {
|
||||
case closure(Closure<URL, WKNavigationActionPolicy>)
|
||||
case simpleResult(WKNavigationActionPolicy)
|
||||
public protocol NavigationPolicy {
|
||||
func policy(for url: URL) -> WKNavigationActionPolicy
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// Copyright (c) 2022 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import enum WebKit.WKNavigationActionPolicy
|
||||
|
||||
open class RegexNavigationPolicy: AnyNavigationPolicy {
|
||||
|
||||
public var regex: NSRegularExpression
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public init(regex: NSRegularExpression) {
|
||||
self.regex = regex
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
public convenience init?(stringRegex: String) {
|
||||
guard let regex = try? NSRegularExpression(pattern: stringRegex) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.init(regex: regex)
|
||||
}
|
||||
|
||||
// MARK: - NavigationPolicy
|
||||
|
||||
open override func policy(for url: URL) -> WKNavigationActionPolicy {
|
||||
url.validate(with: regex) ? .allow : .cancel
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// Copyright (c) 2022 Touch Instinct
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the Software), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import enum WebKit.WKNavigationActionPolicy
|
||||
|
||||
/// Compares URL with combination of URL components.
|
||||
open class URLComponentsNavigationPolicy: AnyNavigationPolicy {
|
||||
|
||||
public var components: [URLComponent]
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public init(components: [URLComponent]) {
|
||||
self.components = components
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: - NavigationPolicy
|
||||
|
||||
open override func policy(for url: URL) -> WKNavigationActionPolicy {
|
||||
components.allSatisfy { url.validate(by: $0) } ? .allow : .cancel
|
||||
}
|
||||
}
|
||||
|
|
@ -24,16 +24,10 @@ import WebKit
|
|||
|
||||
open class BaseWebViewStateHandler: NSObject, WebViewStateHandler {
|
||||
|
||||
public weak var viewModel: WebViewModelProtocol?
|
||||
public weak var viewModel: WebViewModel?
|
||||
|
||||
// MARK: - WebViewStateHandler
|
||||
|
||||
open func navigationProgress(_ progress: Double, isLoading: Bool) {
|
||||
// Override in subclass
|
||||
}
|
||||
|
||||
// MARK: - WKNavigationDelegate
|
||||
|
||||
open func webView(_ webView: WKWebView,
|
||||
decidePolicyFor navigationAction: WKNavigationAction,
|
||||
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
||||
|
|
@ -47,6 +41,7 @@ open class BaseWebViewStateHandler: NSObject, WebViewStateHandler {
|
|||
}
|
||||
|
||||
open func webView(_ webView: WKWebView, didCommit navigation: WKNavigation?) {
|
||||
// override in subviews
|
||||
}
|
||||
|
||||
open func webView(_ webView: WKWebView, didFinish navigation: WKNavigation?) {
|
||||
|
|
@ -56,6 +51,7 @@ open class BaseWebViewStateHandler: NSObject, WebViewStateHandler {
|
|||
open func webView(_ webView: WKWebView,
|
||||
didFailProvisionalNavigation navigation: WKNavigation?,
|
||||
withError error: Error) {
|
||||
|
||||
viewModel?.handleError(error, url: webView.url)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,5 @@
|
|||
import protocol WebKit.WKNavigationDelegate
|
||||
|
||||
public protocol WebViewStateHandler: WKNavigationDelegate {
|
||||
var viewModel: WebViewModelProtocol? { get set }
|
||||
|
||||
func navigationProgress(_ progress: Double, isLoading: Bool)
|
||||
var viewModel: WebViewModel? { get set }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,22 +29,19 @@ public extension URL {
|
|||
return true
|
||||
|
||||
case let .absolutePath(path):
|
||||
return absoluteString == path
|
||||
return compare(by: .absolutePath(path))
|
||||
|
||||
case let .host(host):
|
||||
return self.host == host
|
||||
return compare(by: .host(host))
|
||||
|
||||
case let .query(query):
|
||||
return (self.query ?? "").contains(query)
|
||||
return compare(by: .query(query))
|
||||
|
||||
case let .regex(stringRegex):
|
||||
guard let regex = try? NSRegularExpression(pattern: stringRegex) else {
|
||||
return false
|
||||
}
|
||||
|
||||
let range = NSRange(location: 0, length: absoluteString.utf16.count)
|
||||
|
||||
return regex.firstMatch(in: absoluteString, range: range) != nil
|
||||
return validate(with: regex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TISwiftUtils
|
||||
import TIUIKitCore
|
||||
import WebKit
|
||||
|
||||
|
|
@ -27,23 +28,20 @@ open class BaseInitializableWebView: WKWebView,
|
|||
InitializableViewProtocol,
|
||||
ConfigurableView {
|
||||
|
||||
public var viewModel: WebViewModelProtocol? {
|
||||
public var stateHandler: WebViewStateHandler
|
||||
public var viewModel: WebViewModel? {
|
||||
didSet {
|
||||
stateHandler?.viewModel = viewModel
|
||||
}
|
||||
}
|
||||
public var stateHandler: WebViewStateHandler? {
|
||||
didSet {
|
||||
navigationDelegate = stateHandler
|
||||
stateHandler.viewModel = viewModel
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public init(stateHandler: WebViewStateHandler? = BaseWebViewStateHandler()) {
|
||||
public init(stateHandler: WebViewStateHandler = BaseWebViewStateHandler()) {
|
||||
self.stateHandler = stateHandler
|
||||
|
||||
super.init(frame: .zero, configuration: .init())
|
||||
|
||||
self.stateHandler = stateHandler
|
||||
initializeView()
|
||||
}
|
||||
|
||||
|
|
@ -63,10 +61,7 @@ open class BaseInitializableWebView: WKWebView,
|
|||
}
|
||||
|
||||
public func bindViews() {
|
||||
addObserver(self,
|
||||
forKeyPath: #keyPath(WKWebView.estimatedProgress),
|
||||
options: .new,
|
||||
context: nil)
|
||||
navigationDelegate = stateHandler
|
||||
}
|
||||
|
||||
public func configureAppearance() {
|
||||
|
|
@ -77,23 +72,21 @@ open class BaseInitializableWebView: WKWebView,
|
|||
// override in subviews
|
||||
}
|
||||
|
||||
// MARK: - Overrided methods
|
||||
|
||||
open override func observeValue(forKeyPath keyPath: String?,
|
||||
of object: Any?,
|
||||
change: [NSKeyValueChangeKey : Any]?,
|
||||
context: UnsafeMutableRawPointer?) {
|
||||
|
||||
if keyPath == #keyPath(WKWebView.estimatedProgress) {
|
||||
stateHandler?.navigationProgress(estimatedProgress, isLoading: isLoading)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ConfigurableView
|
||||
|
||||
open func configure(with viewModel: WebViewModelProtocol) {
|
||||
open func configure(with viewModel: WebViewModel) {
|
||||
self.viewModel = viewModel
|
||||
|
||||
configuration.userContentController.add(viewModel, name: WebViewErrorConstants.errorMessageName)
|
||||
}
|
||||
|
||||
// MARK: - Public methods
|
||||
|
||||
public func subscribe(onProgress: ParameterClosure<Double>? = nil) -> NSKeyValueObservation {
|
||||
observe(\.estimatedProgress, options: [.new]) { webView, change in
|
||||
if webView.isLoading, let newValue = change.newValue {
|
||||
onProgress?(newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
import WebKit
|
||||
|
||||
open class BaseWebViewModel: NSObject, WebViewModelProtocol {
|
||||
open class DefaultWebViewModel: NSObject, WebViewModel {
|
||||
|
||||
public var injector: BaseWebViewUrlInjector
|
||||
public var navigator: BaseWebViewNavigator
|
||||
|
|
@ -41,20 +41,6 @@ open class BaseWebViewModel: NSObject, WebViewModelProtocol {
|
|||
super.init()
|
||||
}
|
||||
|
||||
// MARK: - Open methods
|
||||
|
||||
open func makeUrlInjection(forWebView webView: WKWebView) {
|
||||
injector.inject(on: webView)
|
||||
}
|
||||
|
||||
open func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy {
|
||||
navigator.shouldNavigate(toUrl: url)
|
||||
}
|
||||
|
||||
open func handleError(_ error: Error, url: URL?) {
|
||||
errorHandler.didRecievedError(.init(url: url, error: error))
|
||||
}
|
||||
|
||||
// MARK: - WKScriptMessageHandler
|
||||
|
||||
open func userContentController(_ userContentController: WKUserContentController,
|
||||
|
|
@ -68,9 +54,9 @@ open class BaseWebViewModel: NSObject, WebViewModelProtocol {
|
|||
|
||||
// MARK: - Private methods
|
||||
|
||||
private func parseError(_ message: WKScriptMessage) -> WebViewErrorModel {
|
||||
private func parseError(_ message: WKScriptMessage) -> WebViewError {
|
||||
let body = message.body as? [String: Any]
|
||||
let error = body?[WebViewErrorConstants.errorPropertyName] as? String
|
||||
return .init(jsErrorMessage: error)
|
||||
return .jsError(message.webView?.url, error ?? "")
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
import WebKit
|
||||
|
||||
public protocol WebViewModelProtocol: WKScriptMessageHandler {
|
||||
public protocol WebViewModel: WKScriptMessageHandler {
|
||||
var injector: BaseWebViewUrlInjector { get }
|
||||
var navigator: BaseWebViewNavigator { get }
|
||||
var errorHandler: BaseWebViewErrorHandler { get }
|
||||
|
|
@ -31,3 +31,18 @@ public protocol WebViewModelProtocol: WKScriptMessageHandler {
|
|||
func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy
|
||||
func handleError(_ error: Error, url: URL?)
|
||||
}
|
||||
|
||||
public extension WebViewModel {
|
||||
|
||||
func makeUrlInjection(forWebView webView: WKWebView) {
|
||||
injector.inject(on: webView)
|
||||
}
|
||||
|
||||
func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy {
|
||||
navigator.shouldNavigate(toUrl: url)
|
||||
}
|
||||
|
||||
func handleError(_ error: Error, url: URL?) {
|
||||
errorHandler.didRecievedError(.standardError(url, error))
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,5 @@ Pod::Spec.new do |s|
|
|||
|
||||
s.dependency 'TIUIKitCore', s.version.to_s
|
||||
s.dependency 'TISwiftUtils', s.version.to_s
|
||||
s.dependency 'TILogging', s.version.to_s
|
||||
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue