From d12e07484d10520042aa0aa5063f22883f7f1f15 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Mon, 26 Dec 2022 10:36:58 +0300 Subject: [PATCH 01/12] feat: custom webview api --- Package.swift | 2 + .../BaseWebViewErrorHandler.swift | 36 +++++ .../ErrorHandler/WebViewErrorConstants.swift | 31 ++++ .../WebViewErrorHandlerProtocol.swift | 25 ++++ .../ErrorHandler/WebViewErrorModel.swift | 38 +++++ .../BaseWebViewNavigator.swift | 60 ++++++++ .../NavigationHandler/NavigationResult.swift | 30 ++++ .../WebViewNavigationDelegate.swift | 49 +++++++ .../WebViewNavigatorProtocol.swift | 28 ++++ .../URLInjector/BaseWebViewUrlInjector.swift | 92 ++++++++++++ .../URLInjector/Helpers/URL+Comparator.swift | 50 +++++++ .../URLInjector/WebViewUrlComparator.swift | 29 ++++ .../URLInjector/WebViewUrlInjection.swift | 27 ++++ .../WebViewUrlInjectorProtocol.swift | 27 ++++ .../Views/BaseInitializableWebView.swift | 132 ++++++++++++++++++ .../Views/ViewModels/BaseWebViewModel.swift | 77 ++++++++++ .../ViewModels/WebViewModelProtocol.swift | 32 +++++ TIWebView/TIWebView.podspec | 20 +++ 18 files changed, 785 insertions(+) create mode 100644 TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift create mode 100644 TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift create mode 100644 TIWebView/Sources/ErrorHandler/WebViewErrorHandlerProtocol.swift create mode 100644 TIWebView/Sources/ErrorHandler/WebViewErrorModel.swift create mode 100644 TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift create mode 100644 TIWebView/Sources/NavigationHandler/NavigationResult.swift create mode 100644 TIWebView/Sources/NavigationHandler/WebViewNavigationDelegate.swift create mode 100644 TIWebView/Sources/NavigationHandler/WebViewNavigatorProtocol.swift create mode 100644 TIWebView/Sources/URLInjector/BaseWebViewUrlInjector.swift create mode 100644 TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift create mode 100644 TIWebView/Sources/URLInjector/WebViewUrlComparator.swift create mode 100644 TIWebView/Sources/URLInjector/WebViewUrlInjection.swift create mode 100644 TIWebView/Sources/URLInjector/WebViewUrlInjectorProtocol.swift create mode 100644 TIWebView/Sources/Views/BaseInitializableWebView.swift create mode 100644 TIWebView/Sources/Views/ViewModels/BaseWebViewModel.swift create mode 100644 TIWebView/Sources/Views/ViewModels/WebViewModelProtocol.swift create mode 100644 TIWebView/TIWebView.podspec diff --git a/Package.swift b/Package.swift index 87950b8c..8a11bcdc 100644 --- a/Package.swift +++ b/Package.swift @@ -11,6 +11,7 @@ let package = Package( // MARK: - UIKit .library(name: "TIUIKitCore", targets: ["TIUIKitCore"]), .library(name: "TIUIElements", targets: ["TIUIElements"]), + .library(name: "TIWebView", targets: ["TIWebView"]), // MARK: - SwiftUI .library(name: "TISwiftUICore", targets: ["TISwiftUICore"]), @@ -55,6 +56,7 @@ let package = Package( // MARK: - UIKit .target(name: "TIUIKitCore", dependencies: ["TISwiftUtils"], path: "TIUIKitCore/Sources"), .target(name: "TIUIElements", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIUIElements/Sources"), + .target(name: "TIWebView", dependencies: ["TIUIKitCore", "TISwiftUtils", "TILogging"], path: "TIWebView/Sources"), // MARK: - SwiftUI .target(name: "TISwiftUICore", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TISwiftUICore/Sources"), diff --git a/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift b/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift new file mode 100644 index 00000000..1f6fa728 --- /dev/null +++ b/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift @@ -0,0 +1,36 @@ +// +// Copyright (c) 2020 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 TILogging + +open class BaseWebViewErrorHandler: WebViewErrorHandlerProtocol { + + let logger: TILogger? + + public init(logger: TILogger? = nil) { + self.logger = logger + } + + open func didRecievedError(_ error: WebViewErrorModel) { + logger?.error("%@", "\(error)") + } +} diff --git a/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift b/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift new file mode 100644 index 00000000..86cdbc0e --- /dev/null +++ b/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift @@ -0,0 +1,31 @@ +// +// Copyright (c) 2020 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 WebViewErrorConstants { + static var errorMessageName: String { + "error" + } + + static var errorPropertyName: String { + "message" + } +} diff --git a/TIWebView/Sources/ErrorHandler/WebViewErrorHandlerProtocol.swift b/TIWebView/Sources/ErrorHandler/WebViewErrorHandlerProtocol.swift new file mode 100644 index 00000000..3d8c0770 --- /dev/null +++ b/TIWebView/Sources/ErrorHandler/WebViewErrorHandlerProtocol.swift @@ -0,0 +1,25 @@ +// +// Copyright (c) 2020 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 protocol WebViewErrorHandlerProtocol { + func didRecievedError(_ error: WebViewErrorModel) +} diff --git a/TIWebView/Sources/ErrorHandler/WebViewErrorModel.swift b/TIWebView/Sources/ErrorHandler/WebViewErrorModel.swift new file mode 100644 index 00000000..9e1a82a9 --- /dev/null +++ b/TIWebView/Sources/ErrorHandler/WebViewErrorModel.swift @@ -0,0 +1,38 @@ +// +// Copyright (c) 2020 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 + +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 + } +} diff --git a/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift b/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift new file mode 100644 index 00000000..08b46016 --- /dev/null +++ b/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift @@ -0,0 +1,60 @@ +// +// Copyright (c) 2020 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 BaseWebViewNavigator: WebViewNavigatorProtocol { + + public typealias WebViewNavigationMap = [WebViewUrlComparator: NavigationResult] + + public let navigationMap: WebViewNavigationMap + + public init(navigationMap: WebViewNavigationMap) { + self.navigationMap = navigationMap + } + + public convenience init() { + self.init(navigationMap: [:]) + } + + open func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy { + guard !navigationMap.isEmpty else { + 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 + } + } +} diff --git a/TIWebView/Sources/NavigationHandler/NavigationResult.swift b/TIWebView/Sources/NavigationHandler/NavigationResult.swift new file mode 100644 index 00000000..7f8c07ab --- /dev/null +++ b/TIWebView/Sources/NavigationHandler/NavigationResult.swift @@ -0,0 +1,30 @@ +// +// Copyright (c) 2020 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 TISwiftUtils +import enum WebKit.WKNavigationActionPolicy + +public enum NavigationResult { + case closure(Closure) + case simpleResult(WKNavigationActionPolicy) +} diff --git a/TIWebView/Sources/NavigationHandler/WebViewNavigationDelegate.swift b/TIWebView/Sources/NavigationHandler/WebViewNavigationDelegate.swift new file mode 100644 index 00000000..d69fb222 --- /dev/null +++ b/TIWebView/Sources/NavigationHandler/WebViewNavigationDelegate.swift @@ -0,0 +1,49 @@ +// +// Copyright (c) 2020 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 WebKit + +public protocol WebViewNavigationDelegate { + func navigationProgress(_ progress: Double, isLoading: Bool) + func didCommit(_ navigation: WKNavigation, forWebView webView: WKWebView) + func didFinish(_ navigation: WKNavigation!, forWebView webView: WKWebView) + func didFailProvisionalNavigation(_ navigation: WKNavigation!, withError error: Error, forWebView webView: WKWebView) + func didFail(_ navigation: WKNavigation!, withError error: Error, forWebView webView: WKWebView) +} + +public extension WebViewNavigationDelegate { + func navigationProgress(_ progress: Double, isLoading: Bool) { + // empty implementation + } + + func didCommit(_ navigation: WKNavigation, forWebView webView: WKWebView) { + // empty implementation + } + + func didFinish(_ navigation: WKNavigation!, forWebView webView: WKWebView) { + // empty implementation + } + + func didFail(_ navigation: WKNavigation!, withError error: Error, forWebView webView: WKWebView) { + // empty implementation + } +} diff --git a/TIWebView/Sources/NavigationHandler/WebViewNavigatorProtocol.swift b/TIWebView/Sources/NavigationHandler/WebViewNavigatorProtocol.swift new file mode 100644 index 00000000..e6a55a30 --- /dev/null +++ b/TIWebView/Sources/NavigationHandler/WebViewNavigatorProtocol.swift @@ -0,0 +1,28 @@ +// +// Copyright (c) 2020 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 + +public protocol WebViewNavigatorProtocol { + func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy +} diff --git a/TIWebView/Sources/URLInjector/BaseWebViewUrlInjector.swift b/TIWebView/Sources/URLInjector/BaseWebViewUrlInjector.swift new file mode 100644 index 00000000..14b89331 --- /dev/null +++ b/TIWebView/Sources/URLInjector/BaseWebViewUrlInjector.swift @@ -0,0 +1,92 @@ +// +// Copyright (c) 2020 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 class WebKit.WKWebView + +open class BaseWebViewUrlInjector: WebViewUrlInjectorProtocol { + + public typealias URLInjection = [WebViewUrlComparator: [WebViewUrlInjection]] + + public var injection: URLInjection + + public init(injection: URLInjection) { + self.injection = injection + } + + public convenience init() { + self.init(injection: [:]) + } + + open func inject(onWebView webView: WKWebView) { + guard !injection.isEmpty, let url = webView.url else { + return + } + + injection.forEach { (comparator, injections) in + guard url.compare(by: comparator) else { + return + } + + injections.forEach { evaluteInjection(onWebView: webView, injection: $0) } + } + } + + private func evaluteInjection(onWebView webView: WKWebView, injection: WebViewUrlInjection) { + let jsScript = makeJsScript(fromInjection: injection) + + guard !jsScript.isEmpty else { + return + } + + webView.evaluateJavaScript(jsScript, completionHandler: nil) + } + + private func makeJsScript(fromInjection injection: WebViewUrlInjection) -> String { + switch injection { + case let .css(css): + return cssJsScript(css: css) + + case let .cssForFile(file): + guard let path = Bundle.main.path(forResource: file, ofType: "css") else { + return "" + } + + let css = try? String(contentsOfFile: path) + .components(separatedBy: .newlines) + .joined() + + return cssJsScript(css: css ?? "") + + case let .javaScript(script): + return script + } + } + + private func cssJsScript(css: String) -> String { + """ + var style = document.createElement('style'); + style.innerHTML = '\(css)'; + document.head.appendChild(style); + """ + } +} diff --git a/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift b/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift new file mode 100644 index 00000000..1a0e9182 --- /dev/null +++ b/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift @@ -0,0 +1,50 @@ +// +// Copyright (c) 2020 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 + +public extension URL { + func compare(by comparator: WebViewUrlComparator) -> Bool { + switch comparator { + case .any: + return true + + case let .absolutePath(path): + return absoluteString == path + + case let .host(host): + return self.host == host + + case let .query(query): + return (self.query ?? "").contains(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 + } + } +} diff --git a/TIWebView/Sources/URLInjector/WebViewUrlComparator.swift b/TIWebView/Sources/URLInjector/WebViewUrlComparator.swift new file mode 100644 index 00000000..cdfa55d2 --- /dev/null +++ b/TIWebView/Sources/URLInjector/WebViewUrlComparator.swift @@ -0,0 +1,29 @@ +// +// Copyright (c) 2020 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 WebViewUrlComparator: Hashable { + case any + case absolutePath(String) + case host(String) + case query(String) + case regex(String) +} diff --git a/TIWebView/Sources/URLInjector/WebViewUrlInjection.swift b/TIWebView/Sources/URLInjector/WebViewUrlInjection.swift new file mode 100644 index 00000000..9d2ae94a --- /dev/null +++ b/TIWebView/Sources/URLInjector/WebViewUrlInjection.swift @@ -0,0 +1,27 @@ +// +// Copyright (c) 2020 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 WebViewUrlInjection { + case css(String) + case cssForFile(String) + case javaScript(String) +} diff --git a/TIWebView/Sources/URLInjector/WebViewUrlInjectorProtocol.swift b/TIWebView/Sources/URLInjector/WebViewUrlInjectorProtocol.swift new file mode 100644 index 00000000..e9b20ad3 --- /dev/null +++ b/TIWebView/Sources/URLInjector/WebViewUrlInjectorProtocol.swift @@ -0,0 +1,27 @@ +// +// Copyright (c) 2020 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 class WebKit.WKWebView + +public protocol WebViewUrlInjectorProtocol { + func inject(onWebView webView: WKWebView) +} diff --git a/TIWebView/Sources/Views/BaseInitializableWebView.swift b/TIWebView/Sources/Views/BaseInitializableWebView.swift new file mode 100644 index 00000000..ec85ce2b --- /dev/null +++ b/TIWebView/Sources/Views/BaseInitializableWebView.swift @@ -0,0 +1,132 @@ +// +// Copyright (c) 2020 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 TIUIKitCore +import WebKit + +open class BaseInitializableWebView: WKWebView, + InitializableViewProtocol, + ConfigurableView, + WKNavigationDelegate { + + public var viewModel: WebViewModelProtocol? + public var delegate: WebViewNavigationDelegate? + + // MARK: - Init + + public init() { + super.init(frame: .zero, configuration: .init()) + + initializeView() + } + + @available(*, unavailable) + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - InitializableViewProtocol + + public func addViews() { + // override in subviews + } + + public func configureLayout() { + // override in subviews + } + + public func bindViews() { + navigationDelegate = self + configuration.preferences.javaScriptEnabled = true + + addObserver(self, + forKeyPath: #keyPath(WKWebView.estimatedProgress), + options: .new, + context: nil) + } + + public func configureAppearance() { + // override in subviews + } + + public func localize() { + // 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) { + delegate?.navigationProgress(estimatedProgress, isLoading: isLoading) + } + } + + // MARK: - ConfigurableView + + open func configure(with viewModel: WebViewModelProtocol) { + self.viewModel = viewModel + + configuration.userContentController.add(viewModel, name: WebViewErrorConstants.errorMessageName) + } + + // MARK: - WKNavigationDelegate + + open func webView(_ webView: WKWebView, + decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + + guard let url = navigationAction.request.url else { + return decisionHandler(.cancel) + } + + let decision = viewModel?.shouldNavigate(toUrl: url) ?? .cancel + decisionHandler(decision) + } + + open func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { + delegate?.didCommit(navigation, forWebView: webView) + } + + open func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + delegate?.didFinish(navigation, forWebView: webView) + viewModel?.makeUrlInjection(forWebView: webView) + } + + open func webView(_ webView: WKWebView, + didFailProvisionalNavigation navigation: WKNavigation!, + withError error: Error) { + viewModel?.handleError(error, url: webView.url) + delegate?.didFailProvisionalNavigation(navigation, withError: error, forWebView: webView) + } + + open func webView(_ webView: WKWebView, + didFail navigation: WKNavigation!, + withError error: Error) { + + viewModel?.handleError(error, url: webView.url) + delegate?.didFail(navigation, withError: error, forWebView: webView) + } +} diff --git a/TIWebView/Sources/Views/ViewModels/BaseWebViewModel.swift b/TIWebView/Sources/Views/ViewModels/BaseWebViewModel.swift new file mode 100644 index 00000000..9686c61c --- /dev/null +++ b/TIWebView/Sources/Views/ViewModels/BaseWebViewModel.swift @@ -0,0 +1,77 @@ +// +// Copyright (c) 2020 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 WebKit + +open class BaseWebViewModel: NSObject, WebViewModelProtocol { + + public var injector: WebViewUrlInjectorProtocol + public var navigator: WebViewNavigatorProtocol + public var errorHandler: WebViewErrorHandlerProtocol + + // MARK: - Init + + public init(injector: WebViewUrlInjectorProtocol = BaseWebViewUrlInjector(), + navigator: WebViewNavigatorProtocol = BaseWebViewNavigator(), + errorHandler: WebViewErrorHandlerProtocol = BaseWebViewErrorHandler()) { + + self.injector = injector + self.navigator = navigator + self.errorHandler = errorHandler + + super.init() + } + + // MARK: - Open methods + + open func makeUrlInjection(forWebView webView: WKWebView) { + injector.inject(onWebView: 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, + didReceive message: WKScriptMessage) { + + if message.name == WebViewErrorConstants.errorMessageName, + let error = parseError(message){ + let url = message.webView?.url + errorHandler.didRecievedError(.init(url: url, jsErrorMessage: error)) + } + } + + // MARK: - Private methods + + private func parseError(_ message: WKScriptMessage) -> String? { + let body = message.body as? [String: Any] + let error = body?[WebViewErrorConstants.errorPropertyName] as? String + return error + } +} diff --git a/TIWebView/Sources/Views/ViewModels/WebViewModelProtocol.swift b/TIWebView/Sources/Views/ViewModels/WebViewModelProtocol.swift new file mode 100644 index 00000000..6c2efc23 --- /dev/null +++ b/TIWebView/Sources/Views/ViewModels/WebViewModelProtocol.swift @@ -0,0 +1,32 @@ +// +// Copyright (c) 2020 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 WebKit + +public protocol WebViewModelProtocol: WKScriptMessageHandler { + var injector: WebViewUrlInjectorProtocol { get } + var navigator: WebViewNavigatorProtocol { get } + + func makeUrlInjection(forWebView webView: WKWebView) + func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy + func handleError(_ error: Error, url: URL?) +} diff --git a/TIWebView/TIWebView.podspec b/TIWebView/TIWebView.podspec new file mode 100644 index 00000000..dea77855 --- /dev/null +++ b/TIWebView/TIWebView.podspec @@ -0,0 +1,20 @@ +Pod::Spec.new do |s| + s.name = 'TIWebView' + s.version = '1.30.0' + s.summary = 'Universal web view API' + s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru', + 'castlele' => 'nikita.semenov@touchin.ru' } + s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s } + + s.ios.deployment_target = '13.0' + s.swift_versions = ['5.3'] + + s.source_files = s.name + '/Sources/**/*' + + s.dependency 'TIUIKitCore', s.version.to_s + s.dependency 'TISwiftUtils', s.version.to_s + s.dependency 'TILogging', s.version.to_s + +end From 7add7c46ab8f9498775043337fae5b1b5ae725f8 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Mon, 26 Dec 2022 11:19:47 +0300 Subject: [PATCH 02/12] fix: bump podspec version --- CHANGELOG.md | 4 ++++ LeadKit.podspec | 2 +- TIAppleMapUtils/TIAppleMapUtils.podspec | 2 +- TIAuth/TIAuth.podspec | 2 +- TIEcommerce/TIEcommerce.podspec | 2 +- TIFoundationUtils/TIFoundationUtils.podspec | 2 +- TIGoogleMapUtils/TIGoogleMapUtils.podspec | 2 +- TIKeychainUtils/TIKeychainUtils.podspec | 2 +- TILogging/TILogging.podspec | 2 +- TIMapUtils/TIMapUtils.podspec | 2 +- TIMoyaNetworking/TIMoyaNetworking.podspec | 2 +- TINetworking/TINetworking.podspec | 2 +- TINetworkingCache/TINetworkingCache.podspec | 2 +- TIPagination/TIPagination.podspec | 2 +- TISwiftUICore/TISwiftUICore.podspec | 2 +- TISwiftUtils/TISwiftUtils.podspec | 2 +- TITableKitUtils/TITableKitUtils.podspec | 2 +- TITransitions/TITransitions.podspec | 2 +- TIUIElements/TIUIElements.podspec | 2 +- TIUIKitCore/TIUIKitCore.podspec | 2 +- TIWebView/TIWebView.podspec | 2 +- TIYandexMapUtils/TIYandexMapUtils.podspec | 2 +- 22 files changed, 25 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e303738..b4dc634e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### 1.32.0 + +- **Added**: `BaseInitializableWebView`with navigation and error handling api. + ### 1.30.0 - **Added**: Base classes for encryption and decryption user token with pin code or biometry diff --git a/LeadKit.podspec b/LeadKit.podspec index 77369e54..ea371335 100644 --- a/LeadKit.podspec +++ b/LeadKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "LeadKit" - s.version = "1.30.0" + s.version = "1.32.0" s.summary = "iOS framework with a bunch of tools for rapid development" s.homepage = "https://github.com/TouchInstinct/LeadKit" s.license = "Apache License, Version 2.0" diff --git a/TIAppleMapUtils/TIAppleMapUtils.podspec b/TIAppleMapUtils/TIAppleMapUtils.podspec index 80a3f947..07eae3e0 100644 --- a/TIAppleMapUtils/TIAppleMapUtils.podspec +++ b/TIAppleMapUtils/TIAppleMapUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIAppleMapUtils' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Set of helpers for map objects clustering and interacting using Apple MapKit.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIAuth/TIAuth.podspec b/TIAuth/TIAuth.podspec index 75915749..459b7752 100644 --- a/TIAuth/TIAuth.podspec +++ b/TIAuth/TIAuth.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIAuth' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Login, registration, confirmation and other related actions' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIEcommerce/TIEcommerce.podspec b/TIEcommerce/TIEcommerce.podspec index e65a3a19..4bf9f962 100644 --- a/TIEcommerce/TIEcommerce.podspec +++ b/TIEcommerce/TIEcommerce.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIEcommerce' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Cart, products, promocodes, bonuses and other related actions' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIFoundationUtils/TIFoundationUtils.podspec b/TIFoundationUtils/TIFoundationUtils.podspec index 63f45a58..1c3bd68e 100644 --- a/TIFoundationUtils/TIFoundationUtils.podspec +++ b/TIFoundationUtils/TIFoundationUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIFoundationUtils' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Set of helpers for Foundation framework classes.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIGoogleMapUtils/TIGoogleMapUtils.podspec b/TIGoogleMapUtils/TIGoogleMapUtils.podspec index 99e30625..27ad5162 100644 --- a/TIGoogleMapUtils/TIGoogleMapUtils.podspec +++ b/TIGoogleMapUtils/TIGoogleMapUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIGoogleMapUtils' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Set of helpers for map objects clustering and interacting using Google Maps SDK.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIKeychainUtils/TIKeychainUtils.podspec b/TIKeychainUtils/TIKeychainUtils.podspec index b0a6525e..f42b2fcc 100644 --- a/TIKeychainUtils/TIKeychainUtils.podspec +++ b/TIKeychainUtils/TIKeychainUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIKeychainUtils' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Set of helpers for Keychain classes.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TILogging/TILogging.podspec b/TILogging/TILogging.podspec index 20b9d4b6..fd6113b7 100644 --- a/TILogging/TILogging.podspec +++ b/TILogging/TILogging.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TILogging' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Logging API' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIMapUtils/TIMapUtils.podspec b/TIMapUtils/TIMapUtils.podspec index 578a4eb3..20a655f5 100644 --- a/TIMapUtils/TIMapUtils.podspec +++ b/TIMapUtils/TIMapUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIMapUtils' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Set of helpers for map objects clustering and interacting.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIMoyaNetworking/TIMoyaNetworking.podspec b/TIMoyaNetworking/TIMoyaNetworking.podspec index 867865d6..015f1600 100644 --- a/TIMoyaNetworking/TIMoyaNetworking.podspec +++ b/TIMoyaNetworking/TIMoyaNetworking.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIMoyaNetworking' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Moya + Swagger network service.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TINetworking/TINetworking.podspec b/TINetworking/TINetworking.podspec index 8f92f73a..aff3e7b7 100644 --- a/TINetworking/TINetworking.podspec +++ b/TINetworking/TINetworking.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TINetworking' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Swagger-frendly networking layer helpers.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TINetworkingCache/TINetworkingCache.podspec b/TINetworkingCache/TINetworkingCache.podspec index 3131de4f..01cc0173 100644 --- a/TINetworkingCache/TINetworkingCache.podspec +++ b/TINetworkingCache/TINetworkingCache.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TINetworkingCache' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Caching results of EndpointRequests.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIPagination/TIPagination.podspec b/TIPagination/TIPagination.podspec index 1df31c1f..873e449d 100644 --- a/TIPagination/TIPagination.podspec +++ b/TIPagination/TIPagination.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIPagination' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Generic pagination component.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TISwiftUICore/TISwiftUICore.podspec b/TISwiftUICore/TISwiftUICore.podspec index f9a4a99a..d9daa05d 100644 --- a/TISwiftUICore/TISwiftUICore.podspec +++ b/TISwiftUICore/TISwiftUICore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TISwiftUICore' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Core UI elements: protocols, views and helpers.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TISwiftUtils/TISwiftUtils.podspec b/TISwiftUtils/TISwiftUtils.podspec index fc9189e6..03c09634 100644 --- a/TISwiftUtils/TISwiftUtils.podspec +++ b/TISwiftUtils/TISwiftUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TISwiftUtils' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Bunch of useful helpers for Swift development.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TITableKitUtils/TITableKitUtils.podspec b/TITableKitUtils/TITableKitUtils.podspec index fe06f081..5d075dad 100644 --- a/TITableKitUtils/TITableKitUtils.podspec +++ b/TITableKitUtils/TITableKitUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TITableKitUtils' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Set of helpers for TableKit classes.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TITransitions/TITransitions.podspec b/TITransitions/TITransitions.podspec index e27da747..f52f0f60 100644 --- a/TITransitions/TITransitions.podspec +++ b/TITransitions/TITransitions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TITransitions' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Set of custom transitions to present controller. ' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIUIElements/TIUIElements.podspec b/TIUIElements/TIUIElements.podspec index bb4eadba..1d19c699 100644 --- a/TIUIElements/TIUIElements.podspec +++ b/TIUIElements/TIUIElements.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIUIElements' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Bunch of useful protocols and views.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIUIKitCore/TIUIKitCore.podspec b/TIUIKitCore/TIUIKitCore.podspec index 533371cd..10099c4f 100644 --- a/TIUIKitCore/TIUIKitCore.podspec +++ b/TIUIKitCore/TIUIKitCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIUIKitCore' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Core UI elements: protocols, views and helpers.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIWebView/TIWebView.podspec b/TIWebView/TIWebView.podspec index dea77855..17120364 100644 --- a/TIWebView/TIWebView.podspec +++ b/TIWebView/TIWebView.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIWebView' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Universal web view API' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIYandexMapUtils/TIYandexMapUtils.podspec b/TIYandexMapUtils/TIYandexMapUtils.podspec index ff7ea34a..e50f583f 100644 --- a/TIYandexMapUtils/TIYandexMapUtils.podspec +++ b/TIYandexMapUtils/TIYandexMapUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIYandexMapUtils' - s.version = '1.30.0' + s.version = '1.32.0' s.summary = 'Set of helpers for map objects clustering and interacting using Yandex Maps SDK.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } From 9d3bbc9c7130caba891da7f804de614554db9618 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Tue, 27 Dec 2022 00:36:31 +0300 Subject: [PATCH 03/12] fix: code review notes --- .../BaseWebViewErrorHandler.swift | 6 +- .../ErrorHandler/WebViewErrorConstants.swift | 2 +- .../WebViewErrorHandlerProtocol.swift | 25 ------- .../ErrorHandler/WebViewErrorModel.swift | 2 +- .../BaseWebViewNavigator.swift | 4 +- .../NavigationHandler/NavigationResult.swift | 2 +- .../WebViewNavigationDelegate.swift | 49 ------------- .../BaseWebViewStateHandler.swift | 68 +++++++++++++++++++ .../WebViewStateHandler.swift} | 11 +-- .../URLInjector/BaseWebViewUrlInjector.swift | 18 ++--- .../URLInjector/Helpers/URL+Comparator.swift | 2 +- .../URLInjector/WebViewUrlComparator.swift | 2 +- .../URLInjector/WebViewUrlInjection.swift | 6 +- .../WebViewUrlInjectorProtocol.swift | 27 -------- .../Views/BaseInitializableWebView.swift | 63 ++++------------- .../Views/ViewModels/BaseWebViewModel.swift | 27 ++++---- .../ViewModels/WebViewModelProtocol.swift | 7 +- TIWebView/TIWebView.podspec | 2 +- 18 files changed, 130 insertions(+), 193 deletions(-) delete mode 100644 TIWebView/Sources/ErrorHandler/WebViewErrorHandlerProtocol.swift delete mode 100644 TIWebView/Sources/NavigationHandler/WebViewNavigationDelegate.swift create mode 100644 TIWebView/Sources/StateDelegate/BaseWebViewStateHandler.swift rename TIWebView/Sources/{NavigationHandler/WebViewNavigatorProtocol.swift => StateDelegate/WebViewStateHandler.swift} (80%) delete mode 100644 TIWebView/Sources/URLInjector/WebViewUrlInjectorProtocol.swift diff --git a/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift b/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift index 1f6fa728..4e059eaa 100644 --- a/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift +++ b/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 Touch Instinct +// 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 @@ -22,9 +22,9 @@ import TILogging -open class BaseWebViewErrorHandler: WebViewErrorHandlerProtocol { +open class BaseWebViewErrorHandler { - let logger: TILogger? + public var logger: TILogger? public init(logger: TILogger? = nil) { self.logger = logger diff --git a/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift b/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift index 86cdbc0e..5309a45e 100644 --- a/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift +++ b/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 Touch Instinct +// 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 diff --git a/TIWebView/Sources/ErrorHandler/WebViewErrorHandlerProtocol.swift b/TIWebView/Sources/ErrorHandler/WebViewErrorHandlerProtocol.swift deleted file mode 100644 index 3d8c0770..00000000 --- a/TIWebView/Sources/ErrorHandler/WebViewErrorHandlerProtocol.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) 2020 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 protocol WebViewErrorHandlerProtocol { - func didRecievedError(_ error: WebViewErrorModel) -} diff --git a/TIWebView/Sources/ErrorHandler/WebViewErrorModel.swift b/TIWebView/Sources/ErrorHandler/WebViewErrorModel.swift index 9e1a82a9..885d5af9 100644 --- a/TIWebView/Sources/ErrorHandler/WebViewErrorModel.swift +++ b/TIWebView/Sources/ErrorHandler/WebViewErrorModel.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 Touch Instinct +// 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 diff --git a/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift b/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift index 08b46016..7482e0b3 100644 --- a/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift +++ b/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 Touch Instinct +// 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 @@ -23,7 +23,7 @@ import Foundation import enum WebKit.WKNavigationActionPolicy -open class BaseWebViewNavigator: WebViewNavigatorProtocol { +open class BaseWebViewNavigator { public typealias WebViewNavigationMap = [WebViewUrlComparator: NavigationResult] diff --git a/TIWebView/Sources/NavigationHandler/NavigationResult.swift b/TIWebView/Sources/NavigationHandler/NavigationResult.swift index 7f8c07ab..a973ecc9 100644 --- a/TIWebView/Sources/NavigationHandler/NavigationResult.swift +++ b/TIWebView/Sources/NavigationHandler/NavigationResult.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 Touch Instinct +// 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 diff --git a/TIWebView/Sources/NavigationHandler/WebViewNavigationDelegate.swift b/TIWebView/Sources/NavigationHandler/WebViewNavigationDelegate.swift deleted file mode 100644 index d69fb222..00000000 --- a/TIWebView/Sources/NavigationHandler/WebViewNavigationDelegate.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) 2020 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 WebKit - -public protocol WebViewNavigationDelegate { - func navigationProgress(_ progress: Double, isLoading: Bool) - func didCommit(_ navigation: WKNavigation, forWebView webView: WKWebView) - func didFinish(_ navigation: WKNavigation!, forWebView webView: WKWebView) - func didFailProvisionalNavigation(_ navigation: WKNavigation!, withError error: Error, forWebView webView: WKWebView) - func didFail(_ navigation: WKNavigation!, withError error: Error, forWebView webView: WKWebView) -} - -public extension WebViewNavigationDelegate { - func navigationProgress(_ progress: Double, isLoading: Bool) { - // empty implementation - } - - func didCommit(_ navigation: WKNavigation, forWebView webView: WKWebView) { - // empty implementation - } - - func didFinish(_ navigation: WKNavigation!, forWebView webView: WKWebView) { - // empty implementation - } - - func didFail(_ navigation: WKNavigation!, withError error: Error, forWebView webView: WKWebView) { - // empty implementation - } -} diff --git a/TIWebView/Sources/StateDelegate/BaseWebViewStateHandler.swift b/TIWebView/Sources/StateDelegate/BaseWebViewStateHandler.swift new file mode 100644 index 00000000..8ea7cd03 --- /dev/null +++ b/TIWebView/Sources/StateDelegate/BaseWebViewStateHandler.swift @@ -0,0 +1,68 @@ +// +// 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 WebKit + +open class BaseWebViewStateHandler: NSObject, WebViewStateHandler { + + public weak var viewModel: WebViewModelProtocol? + + // 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) { + + guard let url = navigationAction.request.url else { + return decisionHandler(.cancel) + } + + let decision = viewModel?.shouldNavigate(toUrl: url) ?? .cancel + decisionHandler(decision) + } + + open func webView(_ webView: WKWebView, didCommit navigation: WKNavigation?) { + } + + open func webView(_ webView: WKWebView, didFinish navigation: WKNavigation?) { + viewModel?.makeUrlInjection(forWebView: webView) + } + + open func webView(_ webView: WKWebView, + didFailProvisionalNavigation navigation: WKNavigation?, + withError error: Error) { + viewModel?.handleError(error, url: webView.url) + } + + open func webView(_ webView: WKWebView, + didFail navigation: WKNavigation?, + withError error: Error) { + + viewModel?.handleError(error, url: webView.url) + } +} diff --git a/TIWebView/Sources/NavigationHandler/WebViewNavigatorProtocol.swift b/TIWebView/Sources/StateDelegate/WebViewStateHandler.swift similarity index 80% rename from TIWebView/Sources/NavigationHandler/WebViewNavigatorProtocol.swift rename to TIWebView/Sources/StateDelegate/WebViewStateHandler.swift index e6a55a30..7dfc0423 100644 --- a/TIWebView/Sources/NavigationHandler/WebViewNavigatorProtocol.swift +++ b/TIWebView/Sources/StateDelegate/WebViewStateHandler.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 Touch Instinct +// 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 @@ -20,9 +20,10 @@ // THE SOFTWARE. // -import Foundation -import enum WebKit.WKNavigationActionPolicy +import protocol WebKit.WKNavigationDelegate -public protocol WebViewNavigatorProtocol { - func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy +public protocol WebViewStateHandler: WKNavigationDelegate { + var viewModel: WebViewModelProtocol? { get set } + + func navigationProgress(_ progress: Double, isLoading: Bool) } diff --git a/TIWebView/Sources/URLInjector/BaseWebViewUrlInjector.swift b/TIWebView/Sources/URLInjector/BaseWebViewUrlInjector.swift index 14b89331..097fb171 100644 --- a/TIWebView/Sources/URLInjector/BaseWebViewUrlInjector.swift +++ b/TIWebView/Sources/URLInjector/BaseWebViewUrlInjector.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 Touch Instinct +// 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 @@ -23,7 +23,7 @@ import Foundation import class WebKit.WKWebView -open class BaseWebViewUrlInjector: WebViewUrlInjectorProtocol { +open class BaseWebViewUrlInjector { public typealias URLInjection = [WebViewUrlComparator: [WebViewUrlInjection]] @@ -37,7 +37,9 @@ open class BaseWebViewUrlInjector: WebViewUrlInjectorProtocol { self.init(injection: [:]) } - open func inject(onWebView webView: WKWebView) { + // MARK: - Open methods + + open func inject(on webView: WKWebView) { guard !injection.isEmpty, let url = webView.url else { return } @@ -51,6 +53,8 @@ open class BaseWebViewUrlInjector: WebViewUrlInjectorProtocol { } } + // MARK: - Private methods + private func evaluteInjection(onWebView webView: WKWebView, injection: WebViewUrlInjection) { let jsScript = makeJsScript(fromInjection: injection) @@ -66,12 +70,8 @@ open class BaseWebViewUrlInjector: WebViewUrlInjectorProtocol { case let .css(css): return cssJsScript(css: css) - case let .cssForFile(file): - guard let path = Bundle.main.path(forResource: file, ofType: "css") else { - return "" - } - - let css = try? String(contentsOfFile: path) + case let .cssForFile(url): + let css = try? String(contentsOf: url) .components(separatedBy: .newlines) .joined() diff --git a/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift b/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift index 1a0e9182..ed562896 100644 --- a/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift +++ b/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 Touch Instinct +// 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 diff --git a/TIWebView/Sources/URLInjector/WebViewUrlComparator.swift b/TIWebView/Sources/URLInjector/WebViewUrlComparator.swift index cdfa55d2..192c04f8 100644 --- a/TIWebView/Sources/URLInjector/WebViewUrlComparator.swift +++ b/TIWebView/Sources/URLInjector/WebViewUrlComparator.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 Touch Instinct +// 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 diff --git a/TIWebView/Sources/URLInjector/WebViewUrlInjection.swift b/TIWebView/Sources/URLInjector/WebViewUrlInjection.swift index 9d2ae94a..d2c7f228 100644 --- a/TIWebView/Sources/URLInjector/WebViewUrlInjection.swift +++ b/TIWebView/Sources/URLInjector/WebViewUrlInjection.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 Touch Instinct +// 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 @@ -20,8 +20,10 @@ // THE SOFTWARE. // +import Foundation + public enum WebViewUrlInjection { case css(String) - case cssForFile(String) + case cssForFile(URL) case javaScript(String) } diff --git a/TIWebView/Sources/URLInjector/WebViewUrlInjectorProtocol.swift b/TIWebView/Sources/URLInjector/WebViewUrlInjectorProtocol.swift deleted file mode 100644 index e9b20ad3..00000000 --- a/TIWebView/Sources/URLInjector/WebViewUrlInjectorProtocol.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2020 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 class WebKit.WKWebView - -public protocol WebViewUrlInjectorProtocol { - func inject(onWebView webView: WKWebView) -} diff --git a/TIWebView/Sources/Views/BaseInitializableWebView.swift b/TIWebView/Sources/Views/BaseInitializableWebView.swift index ec85ce2b..3907a332 100644 --- a/TIWebView/Sources/Views/BaseInitializableWebView.swift +++ b/TIWebView/Sources/Views/BaseInitializableWebView.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 Touch Instinct +// 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 @@ -25,17 +25,25 @@ import WebKit open class BaseInitializableWebView: WKWebView, InitializableViewProtocol, - ConfigurableView, - WKNavigationDelegate { + ConfigurableView { - public var viewModel: WebViewModelProtocol? - public var delegate: WebViewNavigationDelegate? + public var viewModel: WebViewModelProtocol? { + didSet { + stateHandler?.viewModel = viewModel + } + } + public var stateHandler: WebViewStateHandler? { + didSet { + navigationDelegate = stateHandler + } + } // MARK: - Init - public init() { + public init(stateHandler: WebViewStateHandler? = BaseWebViewStateHandler()) { super.init(frame: .zero, configuration: .init()) + self.stateHandler = stateHandler initializeView() } @@ -55,9 +63,6 @@ open class BaseInitializableWebView: WKWebView, } public func bindViews() { - navigationDelegate = self - configuration.preferences.javaScriptEnabled = true - addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, @@ -80,7 +85,7 @@ open class BaseInitializableWebView: WKWebView, context: UnsafeMutableRawPointer?) { if keyPath == #keyPath(WKWebView.estimatedProgress) { - delegate?.navigationProgress(estimatedProgress, isLoading: isLoading) + stateHandler?.navigationProgress(estimatedProgress, isLoading: isLoading) } } @@ -91,42 +96,4 @@ open class BaseInitializableWebView: WKWebView, configuration.userContentController.add(viewModel, name: WebViewErrorConstants.errorMessageName) } - - // MARK: - WKNavigationDelegate - - open func webView(_ webView: WKWebView, - decidePolicyFor navigationAction: WKNavigationAction, - decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - - guard let url = navigationAction.request.url else { - return decisionHandler(.cancel) - } - - let decision = viewModel?.shouldNavigate(toUrl: url) ?? .cancel - decisionHandler(decision) - } - - open func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { - delegate?.didCommit(navigation, forWebView: webView) - } - - open func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - delegate?.didFinish(navigation, forWebView: webView) - viewModel?.makeUrlInjection(forWebView: webView) - } - - open func webView(_ webView: WKWebView, - didFailProvisionalNavigation navigation: WKNavigation!, - withError error: Error) { - viewModel?.handleError(error, url: webView.url) - delegate?.didFailProvisionalNavigation(navigation, withError: error, forWebView: webView) - } - - open func webView(_ webView: WKWebView, - didFail navigation: WKNavigation!, - withError error: Error) { - - viewModel?.handleError(error, url: webView.url) - delegate?.didFail(navigation, withError: error, forWebView: webView) - } } diff --git a/TIWebView/Sources/Views/ViewModels/BaseWebViewModel.swift b/TIWebView/Sources/Views/ViewModels/BaseWebViewModel.swift index 9686c61c..3ad6cbed 100644 --- a/TIWebView/Sources/Views/ViewModels/BaseWebViewModel.swift +++ b/TIWebView/Sources/Views/ViewModels/BaseWebViewModel.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 Touch Instinct +// 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 @@ -24,15 +24,15 @@ import WebKit open class BaseWebViewModel: NSObject, WebViewModelProtocol { - public var injector: WebViewUrlInjectorProtocol - public var navigator: WebViewNavigatorProtocol - public var errorHandler: WebViewErrorHandlerProtocol + public var injector: BaseWebViewUrlInjector + public var navigator: BaseWebViewNavigator + public var errorHandler: BaseWebViewErrorHandler // MARK: - Init - public init(injector: WebViewUrlInjectorProtocol = BaseWebViewUrlInjector(), - navigator: WebViewNavigatorProtocol = BaseWebViewNavigator(), - errorHandler: WebViewErrorHandlerProtocol = BaseWebViewErrorHandler()) { + public init(injector: BaseWebViewUrlInjector = .init(), + navigator: BaseWebViewNavigator = .init(), + errorHandler: BaseWebViewErrorHandler = .init()) { self.injector = injector self.navigator = navigator @@ -44,7 +44,7 @@ open class BaseWebViewModel: NSObject, WebViewModelProtocol { // MARK: - Open methods open func makeUrlInjection(forWebView webView: WKWebView) { - injector.inject(onWebView: webView) + injector.inject(on: webView) } open func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy { @@ -60,18 +60,17 @@ open class BaseWebViewModel: NSObject, WebViewModelProtocol { open func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - if message.name == WebViewErrorConstants.errorMessageName, - let error = parseError(message){ - let url = message.webView?.url - errorHandler.didRecievedError(.init(url: url, jsErrorMessage: error)) + if message.name == WebViewErrorConstants.errorMessageName { + let error = parseError(message) + errorHandler.didRecievedError(error) } } // MARK: - Private methods - private func parseError(_ message: WKScriptMessage) -> String? { + private func parseError(_ message: WKScriptMessage) -> WebViewErrorModel { let body = message.body as? [String: Any] let error = body?[WebViewErrorConstants.errorPropertyName] as? String - return error + return .init(jsErrorMessage: error) } } diff --git a/TIWebView/Sources/Views/ViewModels/WebViewModelProtocol.swift b/TIWebView/Sources/Views/ViewModels/WebViewModelProtocol.swift index 6c2efc23..1542a233 100644 --- a/TIWebView/Sources/Views/ViewModels/WebViewModelProtocol.swift +++ b/TIWebView/Sources/Views/ViewModels/WebViewModelProtocol.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 Touch Instinct +// 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 @@ -23,8 +23,9 @@ import WebKit public protocol WebViewModelProtocol: WKScriptMessageHandler { - var injector: WebViewUrlInjectorProtocol { get } - var navigator: WebViewNavigatorProtocol { get } + var injector: BaseWebViewUrlInjector { get } + var navigator: BaseWebViewNavigator { get } + var errorHandler: BaseWebViewErrorHandler { get } func makeUrlInjection(forWebView webView: WKWebView) func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy diff --git a/TIWebView/TIWebView.podspec b/TIWebView/TIWebView.podspec index 17120364..dec497e8 100644 --- a/TIWebView/TIWebView.podspec +++ b/TIWebView/TIWebView.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| 'castlele' => 'nikita.semenov@touchin.ru' } s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s } - s.ios.deployment_target = '13.0' + s.ios.deployment_target = '11.0' s.swift_versions = ['5.3'] s.source_files = s.name + '/Sources/**/*' From b98678b235fd81e3533dd10281f8fcc07d0db2dd Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Fri, 30 Dec 2022 02:24:19 +0300 Subject: [PATCH 04/12] fix: code review notes --- .../BaseWebViewErrorHandler.swift | 8 ++- .../ErrorHandler/WebViewErrorModel.swift | 16 ++---- .../BaseWebViewNavigator.swift | 24 ++------- .../Helpers/URL+Validation.swift | 44 ++++++++++++++++ .../Helpers/URLComponent.swift | 27 ++++++++++ .../AnyNavigationPolicy.swift | 35 +++++++++++++ .../NavigationPolicy.swift} | 6 +-- .../RegexNavigationPolicy.swift | 51 +++++++++++++++++++ .../URLComponentsNavigationPolicy.swift | 44 ++++++++++++++++ .../BaseWebViewStateHandler.swift | 10 ++-- .../StateDelegate/WebViewStateHandler.swift | 4 +- .../URLInjector/Helpers/URL+Comparator.swift | 11 ++-- .../Views/BaseInitializableWebView.swift | 45 +++++++--------- ...wModel.swift => DefaultWebViewModel.swift} | 20 ++------ ...ModelProtocol.swift => WebViewModel.swift} | 17 ++++++- TIWebView/TIWebView.podspec | 1 - 16 files changed, 260 insertions(+), 103 deletions(-) create mode 100644 TIWebView/Sources/NavigationHandler/Helpers/URL+Validation.swift create mode 100644 TIWebView/Sources/NavigationHandler/Helpers/URLComponent.swift create mode 100644 TIWebView/Sources/NavigationHandler/NavigationPolicy/AnyNavigationPolicy.swift rename TIWebView/Sources/NavigationHandler/{NavigationResult.swift => NavigationPolicy/NavigationPolicy.swift} (88%) create mode 100644 TIWebView/Sources/NavigationHandler/NavigationPolicy/RegexNavigationPolicy.swift create mode 100644 TIWebView/Sources/NavigationHandler/NavigationPolicy/URLComponentsNavigationPolicy.swift rename TIWebView/Sources/Views/ViewModels/{BaseWebViewModel.swift => DefaultWebViewModel.swift} (81%) rename TIWebView/Sources/Views/ViewModels/{WebViewModelProtocol.swift => WebViewModel.swift} (77%) diff --git a/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift b/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift index 4e059eaa..696b860d 100644 --- a/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift +++ b/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift @@ -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 } } diff --git a/TIWebView/Sources/ErrorHandler/WebViewErrorModel.swift b/TIWebView/Sources/ErrorHandler/WebViewErrorModel.swift index 885d5af9..9de11b09 100644 --- a/TIWebView/Sources/ErrorHandler/WebViewErrorModel.swift +++ b/TIWebView/Sources/ErrorHandler/WebViewErrorModel.swift @@ -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) } diff --git a/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift b/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift index 7482e0b3..154763b0 100644 --- a/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift +++ b/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift @@ -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 } } diff --git a/TIWebView/Sources/NavigationHandler/Helpers/URL+Validation.swift b/TIWebView/Sources/NavigationHandler/Helpers/URL+Validation.swift new file mode 100644 index 00000000..844160ce --- /dev/null +++ b/TIWebView/Sources/NavigationHandler/Helpers/URL+Validation.swift @@ -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) + } + } +} diff --git a/TIWebView/Sources/NavigationHandler/Helpers/URLComponent.swift b/TIWebView/Sources/NavigationHandler/Helpers/URLComponent.swift new file mode 100644 index 00000000..df504403 --- /dev/null +++ b/TIWebView/Sources/NavigationHandler/Helpers/URLComponent.swift @@ -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) +} diff --git a/TIWebView/Sources/NavigationHandler/NavigationPolicy/AnyNavigationPolicy.swift b/TIWebView/Sources/NavigationHandler/NavigationPolicy/AnyNavigationPolicy.swift new file mode 100644 index 00000000..4d57c7f8 --- /dev/null +++ b/TIWebView/Sources/NavigationHandler/NavigationPolicy/AnyNavigationPolicy.swift @@ -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 + } +} diff --git a/TIWebView/Sources/NavigationHandler/NavigationResult.swift b/TIWebView/Sources/NavigationHandler/NavigationPolicy/NavigationPolicy.swift similarity index 88% rename from TIWebView/Sources/NavigationHandler/NavigationResult.swift rename to TIWebView/Sources/NavigationHandler/NavigationPolicy/NavigationPolicy.swift index a973ecc9..57b013d4 100644 --- a/TIWebView/Sources/NavigationHandler/NavigationResult.swift +++ b/TIWebView/Sources/NavigationHandler/NavigationPolicy/NavigationPolicy.swift @@ -21,10 +21,8 @@ // import Foundation -import TISwiftUtils import enum WebKit.WKNavigationActionPolicy -public enum NavigationResult { - case closure(Closure) - case simpleResult(WKNavigationActionPolicy) +public protocol NavigationPolicy { + func policy(for url: URL) -> WKNavigationActionPolicy } diff --git a/TIWebView/Sources/NavigationHandler/NavigationPolicy/RegexNavigationPolicy.swift b/TIWebView/Sources/NavigationHandler/NavigationPolicy/RegexNavigationPolicy.swift new file mode 100644 index 00000000..4c51c362 --- /dev/null +++ b/TIWebView/Sources/NavigationHandler/NavigationPolicy/RegexNavigationPolicy.swift @@ -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 + } +} diff --git a/TIWebView/Sources/NavigationHandler/NavigationPolicy/URLComponentsNavigationPolicy.swift b/TIWebView/Sources/NavigationHandler/NavigationPolicy/URLComponentsNavigationPolicy.swift new file mode 100644 index 00000000..4a207ff4 --- /dev/null +++ b/TIWebView/Sources/NavigationHandler/NavigationPolicy/URLComponentsNavigationPolicy.swift @@ -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 + } +} diff --git a/TIWebView/Sources/StateDelegate/BaseWebViewStateHandler.swift b/TIWebView/Sources/StateDelegate/BaseWebViewStateHandler.swift index 8ea7cd03..2c661817 100644 --- a/TIWebView/Sources/StateDelegate/BaseWebViewStateHandler.swift +++ b/TIWebView/Sources/StateDelegate/BaseWebViewStateHandler.swift @@ -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) } diff --git a/TIWebView/Sources/StateDelegate/WebViewStateHandler.swift b/TIWebView/Sources/StateDelegate/WebViewStateHandler.swift index 7dfc0423..6d084f15 100644 --- a/TIWebView/Sources/StateDelegate/WebViewStateHandler.swift +++ b/TIWebView/Sources/StateDelegate/WebViewStateHandler.swift @@ -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 } } diff --git a/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift b/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift index ed562896..e0968ca9 100644 --- a/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift +++ b/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift @@ -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) } } } diff --git a/TIWebView/Sources/Views/BaseInitializableWebView.swift b/TIWebView/Sources/Views/BaseInitializableWebView.swift index 3907a332..e73ebdd8 100644 --- a/TIWebView/Sources/Views/BaseInitializableWebView.swift +++ b/TIWebView/Sources/Views/BaseInitializableWebView.swift @@ -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? = nil) -> NSKeyValueObservation { + observe(\.estimatedProgress, options: [.new]) { webView, change in + if webView.isLoading, let newValue = change.newValue { + onProgress?(newValue) + } + } + } } diff --git a/TIWebView/Sources/Views/ViewModels/BaseWebViewModel.swift b/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift similarity index 81% rename from TIWebView/Sources/Views/ViewModels/BaseWebViewModel.swift rename to TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift index 3ad6cbed..4d62c64a 100644 --- a/TIWebView/Sources/Views/ViewModels/BaseWebViewModel.swift +++ b/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift @@ -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 ?? "") } } diff --git a/TIWebView/Sources/Views/ViewModels/WebViewModelProtocol.swift b/TIWebView/Sources/Views/ViewModels/WebViewModel.swift similarity index 77% rename from TIWebView/Sources/Views/ViewModels/WebViewModelProtocol.swift rename to TIWebView/Sources/Views/ViewModels/WebViewModel.swift index 1542a233..5250bc69 100644 --- a/TIWebView/Sources/Views/ViewModels/WebViewModelProtocol.swift +++ b/TIWebView/Sources/Views/ViewModels/WebViewModel.swift @@ -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)) + } +} diff --git a/TIWebView/TIWebView.podspec b/TIWebView/TIWebView.podspec index dec497e8..aca8bb71 100644 --- a/TIWebView/TIWebView.podspec +++ b/TIWebView/TIWebView.podspec @@ -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 From c75ff4c1d0dda5bb295edeca6cb7dbc4a70116c4 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Fri, 30 Dec 2022 17:15:46 +0700 Subject: [PATCH 05/12] fix: code review notes --- Package.swift | 2 +- .../BaseWebViewErrorHandler.swift | 8 +- .../WebViewError.swift} | 5 +- .../WebViewError/WebViewJSError.swift | 41 +++++++++ .../WebViewError/WebViewLoadingError.swift | 33 +++++++ .../ErrorHandler/WebViewErrorConstants.swift | 14 ++- .../ErrorHandler/WebViewErrorHandler.swift | 31 +++++++ .../BaseWebViewNavigator.swift | 16 +--- .../Helpers/URL+Validation.swift | 8 +- ...wift => AlwaysAllowNavigationPolicy.swift} | 2 +- .../RegexNavigationPolicy.swift | 4 +- .../URLComponentsNavigationPolicy.swift | 2 +- .../NavigationHandler/WebViewNavigator.swift | 41 +++++++++ .../BaseWebViewStateHandler.swift | 6 +- .../URLInjector/BaseWebViewUrlInjector.swift | 57 +----------- .../URLInjector/Helpers/URL+Comparator.swift | 7 +- .../URLInjector/WebViewUrlComparator.swift | 4 +- .../URLInjector/WebViewUrlInjector.swift | 86 +++++++++++++++++++ .../ViewModels/DefaultWebViewModel.swift | 18 ++-- .../Views/ViewModels/WebViewModel.swift | 21 ++--- 20 files changed, 290 insertions(+), 116 deletions(-) rename TIWebView/Sources/ErrorHandler/{WebViewErrorModel.swift => WebViewError/WebViewError.swift} (91%) create mode 100644 TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift create mode 100644 TIWebView/Sources/ErrorHandler/WebViewError/WebViewLoadingError.swift create mode 100644 TIWebView/Sources/ErrorHandler/WebViewErrorHandler.swift rename TIWebView/Sources/NavigationHandler/NavigationPolicy/{AnyNavigationPolicy.swift => AlwaysAllowNavigationPolicy.swift} (95%) create mode 100644 TIWebView/Sources/NavigationHandler/WebViewNavigator.swift create mode 100644 TIWebView/Sources/URLInjector/WebViewUrlInjector.swift diff --git a/Package.swift b/Package.swift index 8a11bcdc..63305190 100644 --- a/Package.swift +++ b/Package.swift @@ -56,7 +56,7 @@ let package = Package( // MARK: - UIKit .target(name: "TIUIKitCore", dependencies: ["TISwiftUtils"], path: "TIUIKitCore/Sources"), .target(name: "TIUIElements", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIUIElements/Sources"), - .target(name: "TIWebView", dependencies: ["TIUIKitCore", "TISwiftUtils", "TILogging"], path: "TIWebView/Sources"), + .target(name: "TIWebView", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIWebView/Sources"), // MARK: - SwiftUI .target(name: "TISwiftUICore", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TISwiftUICore/Sources"), diff --git a/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift b/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift index 696b860d..cbe68ac9 100644 --- a/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift +++ b/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift @@ -20,15 +20,9 @@ // THE SOFTWARE. // -import TILogging - -open class BaseWebViewErrorHandler { +open class BaseWebViewErrorHandler: WebViewErrorHandler { public init() { } - - open func didRecievedError(_ error: WebViewError) { - // override in subviews - } } diff --git a/TIWebView/Sources/ErrorHandler/WebViewErrorModel.swift b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewError.swift similarity index 91% rename from TIWebView/Sources/ErrorHandler/WebViewErrorModel.swift rename to TIWebView/Sources/ErrorHandler/WebViewError/WebViewError.swift index 9de11b09..1cc49cd1 100644 --- a/TIWebView/Sources/ErrorHandler/WebViewErrorModel.swift +++ b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewError.swift @@ -22,7 +22,6 @@ import Foundation -public enum WebViewError: Error { - case standardError(URL?, Error) - case jsError(URL?, String) +public protocol WebViewError: Error { + var contentURL: URL? { get } } diff --git a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift new file mode 100644 index 00000000..d30c673a --- /dev/null +++ b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift @@ -0,0 +1,41 @@ +// +// 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 + +public struct WebViewJSError: WebViewError, Codable { + public let contentURL: URL? + public let name: String? + public let message: String? + public let stackTrace: String? + + public init(contentURL: URL?, + name: String?, + message: String?, + stackTrace: String?) { + + self.contentURL = contentURL + self.name = name + self.message = message + self.stackTrace = stackTrace + } +} diff --git a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewLoadingError.swift b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewLoadingError.swift new file mode 100644 index 00000000..5f8a10c9 --- /dev/null +++ b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewLoadingError.swift @@ -0,0 +1,33 @@ +// +// 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 + +public struct WebViewLoadingError: WebViewError { + public let contentURL: URL? + public let innerError: Error + + public init(contentURL: URL?, innerError: Error) { + self.contentURL = contentURL + self.innerError = innerError + } +} diff --git a/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift b/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift index 5309a45e..07d266e0 100644 --- a/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift +++ b/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift @@ -25,7 +25,19 @@ public enum WebViewErrorConstants { "error" } - static var errorPropertyName: String { + static var errorMessage: String { "message" } + + static var errorName: String { + "name" + } + + static var errorUrl: String { + "url" + } + + static var errorStack: String { + "stack" + } } diff --git a/TIWebView/Sources/ErrorHandler/WebViewErrorHandler.swift b/TIWebView/Sources/ErrorHandler/WebViewErrorHandler.swift new file mode 100644 index 00000000..97ace4df --- /dev/null +++ b/TIWebView/Sources/ErrorHandler/WebViewErrorHandler.swift @@ -0,0 +1,31 @@ +// +// 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 protocol WebViewErrorHandler { + func didRecievedError(_ error: WebViewError) +} + +public extension WebViewErrorHandler { + func didRecievedError(_ error: WebViewError) { + // override in subclasses + } +} diff --git a/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift b/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift index 154763b0..57095021 100644 --- a/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift +++ b/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift @@ -20,12 +20,9 @@ // THE SOFTWARE. // -import Foundation -import enum WebKit.WKNavigationActionPolicy +open class BaseWebViewNavigator: WebViewNavigator { -open class BaseWebViewNavigator { - - public let navigationMap: [NavigationPolicy] + public var navigationMap: [NavigationPolicy] public init(navigationMap: [NavigationPolicy]) { self.navigationMap = navigationMap @@ -34,13 +31,4 @@ open class BaseWebViewNavigator { public convenience init() { self.init(navigationMap: []) } - - open func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy { - guard !navigationMap.isEmpty else { - return .cancel - } - - let allowPolicy = navigationMap.filter { $0.policy(for: url) == .allow } - return allowPolicy.isEmpty ? .cancel : .allow - } } diff --git a/TIWebView/Sources/NavigationHandler/Helpers/URL+Validation.swift b/TIWebView/Sources/NavigationHandler/Helpers/URL+Validation.swift index 844160ce..bcbe76ca 100644 --- a/TIWebView/Sources/NavigationHandler/Helpers/URL+Validation.swift +++ b/TIWebView/Sources/NavigationHandler/Helpers/URL+Validation.swift @@ -23,9 +23,8 @@ import Foundation extension URL { - func validate(with regex: NSRegularExpression) -> Bool { + func matches(_ regex: NSRegularExpression) -> Bool { let range = NSRange(location: 0, length: absoluteString.utf16.count) - return regex.firstMatch(in: absoluteString, range: range) != nil } @@ -38,7 +37,10 @@ extension URL { return absoluteString == path case let .query(query): - return (self.query ?? "").contains(query) + if let urlQuery = self.query { + return urlQuery.contains(query) + } + return false } } } diff --git a/TIWebView/Sources/NavigationHandler/NavigationPolicy/AnyNavigationPolicy.swift b/TIWebView/Sources/NavigationHandler/NavigationPolicy/AlwaysAllowNavigationPolicy.swift similarity index 95% rename from TIWebView/Sources/NavigationHandler/NavigationPolicy/AnyNavigationPolicy.swift rename to TIWebView/Sources/NavigationHandler/NavigationPolicy/AlwaysAllowNavigationPolicy.swift index 4d57c7f8..4916580b 100644 --- a/TIWebView/Sources/NavigationHandler/NavigationPolicy/AnyNavigationPolicy.swift +++ b/TIWebView/Sources/NavigationHandler/NavigationPolicy/AlwaysAllowNavigationPolicy.swift @@ -23,7 +23,7 @@ import Foundation import enum WebKit.WKNavigationActionPolicy -open class AnyNavigationPolicy: NavigationPolicy { +open class AlwaysAllowNavigationPolicy: NavigationPolicy { public init() { diff --git a/TIWebView/Sources/NavigationHandler/NavigationPolicy/RegexNavigationPolicy.swift b/TIWebView/Sources/NavigationHandler/NavigationPolicy/RegexNavigationPolicy.swift index 4c51c362..40d9a8c7 100644 --- a/TIWebView/Sources/NavigationHandler/NavigationPolicy/RegexNavigationPolicy.swift +++ b/TIWebView/Sources/NavigationHandler/NavigationPolicy/RegexNavigationPolicy.swift @@ -23,7 +23,7 @@ import Foundation import enum WebKit.WKNavigationActionPolicy -open class RegexNavigationPolicy: AnyNavigationPolicy { +open class RegexNavigationPolicy: AlwaysAllowNavigationPolicy { public var regex: NSRegularExpression @@ -46,6 +46,6 @@ open class RegexNavigationPolicy: AnyNavigationPolicy { // MARK: - NavigationPolicy open override func policy(for url: URL) -> WKNavigationActionPolicy { - url.validate(with: regex) ? .allow : .cancel + url.matches(regex) ? .allow : .cancel } } diff --git a/TIWebView/Sources/NavigationHandler/NavigationPolicy/URLComponentsNavigationPolicy.swift b/TIWebView/Sources/NavigationHandler/NavigationPolicy/URLComponentsNavigationPolicy.swift index 4a207ff4..568ddb11 100644 --- a/TIWebView/Sources/NavigationHandler/NavigationPolicy/URLComponentsNavigationPolicy.swift +++ b/TIWebView/Sources/NavigationHandler/NavigationPolicy/URLComponentsNavigationPolicy.swift @@ -24,7 +24,7 @@ import Foundation import enum WebKit.WKNavigationActionPolicy /// Compares URL with combination of URL components. -open class URLComponentsNavigationPolicy: AnyNavigationPolicy { +open class URLComponentsNavigationPolicy: AlwaysAllowNavigationPolicy { public var components: [URLComponent] diff --git a/TIWebView/Sources/NavigationHandler/WebViewNavigator.swift b/TIWebView/Sources/NavigationHandler/WebViewNavigator.swift new file mode 100644 index 00000000..303f7782 --- /dev/null +++ b/TIWebView/Sources/NavigationHandler/WebViewNavigator.swift @@ -0,0 +1,41 @@ +// +// 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 + +public protocol WebViewNavigator { + var navigationMap: [NavigationPolicy] { get set } + + func shouldNavigate(to url: URL) -> WKNavigationActionPolicy +} + +public extension WebViewNavigator { + func shouldNavigate(to url: URL) -> WKNavigationActionPolicy { + guard !navigationMap.isEmpty else { + return .cancel + } + + let allowPolicy = navigationMap.filter { $0.policy(for: url) == .allow } + return allowPolicy.isEmpty ? .cancel : .allow + } +} diff --git a/TIWebView/Sources/StateDelegate/BaseWebViewStateHandler.swift b/TIWebView/Sources/StateDelegate/BaseWebViewStateHandler.swift index 2c661817..6cd66fae 100644 --- a/TIWebView/Sources/StateDelegate/BaseWebViewStateHandler.swift +++ b/TIWebView/Sources/StateDelegate/BaseWebViewStateHandler.swift @@ -36,16 +36,16 @@ open class BaseWebViewStateHandler: NSObject, WebViewStateHandler { return decisionHandler(.cancel) } - let decision = viewModel?.shouldNavigate(toUrl: url) ?? .cancel + let decision = viewModel?.shouldNavigate(to: url) ?? .cancel decisionHandler(decision) } open func webView(_ webView: WKWebView, didCommit navigation: WKNavigation?) { - // override in subviews + // override in subclasses } open func webView(_ webView: WKWebView, didFinish navigation: WKNavigation?) { - viewModel?.makeUrlInjection(forWebView: webView) + viewModel?.makeUrlInjection(into: webView) } open func webView(_ webView: WKWebView, diff --git a/TIWebView/Sources/URLInjector/BaseWebViewUrlInjector.swift b/TIWebView/Sources/URLInjector/BaseWebViewUrlInjector.swift index 097fb171..2429a2d7 100644 --- a/TIWebView/Sources/URLInjector/BaseWebViewUrlInjector.swift +++ b/TIWebView/Sources/URLInjector/BaseWebViewUrlInjector.swift @@ -23,9 +23,7 @@ import Foundation import class WebKit.WKWebView -open class BaseWebViewUrlInjector { - - public typealias URLInjection = [WebViewUrlComparator: [WebViewUrlInjection]] +open class BaseWebViewUrlInjector: WebViewUrlInjector { public var injection: URLInjection @@ -36,57 +34,4 @@ open class BaseWebViewUrlInjector { public convenience init() { self.init(injection: [:]) } - - // MARK: - Open methods - - open func inject(on webView: WKWebView) { - guard !injection.isEmpty, let url = webView.url else { - return - } - - injection.forEach { (comparator, injections) in - guard url.compare(by: comparator) else { - return - } - - injections.forEach { evaluteInjection(onWebView: webView, injection: $0) } - } - } - - // MARK: - Private methods - - private func evaluteInjection(onWebView webView: WKWebView, injection: WebViewUrlInjection) { - let jsScript = makeJsScript(fromInjection: injection) - - guard !jsScript.isEmpty else { - return - } - - webView.evaluateJavaScript(jsScript, completionHandler: nil) - } - - private func makeJsScript(fromInjection injection: WebViewUrlInjection) -> String { - switch injection { - case let .css(css): - return cssJsScript(css: css) - - case let .cssForFile(url): - let css = try? String(contentsOf: url) - .components(separatedBy: .newlines) - .joined() - - return cssJsScript(css: css ?? "") - - case let .javaScript(script): - return script - } - } - - private func cssJsScript(css: String) -> String { - """ - var style = document.createElement('style'); - style.innerHTML = '\(css)'; - document.head.appendChild(style); - """ - } } diff --git a/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift b/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift index e0968ca9..6c32c718 100644 --- a/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift +++ b/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift @@ -37,11 +37,8 @@ public extension URL { case let .query(query): return compare(by: .query(query)) - case let .regex(stringRegex): - guard let regex = try? NSRegularExpression(pattern: stringRegex) else { - return false - } - return validate(with: regex) + case let .regex(nsRegex): + return matches(nsRegex) } } } diff --git a/TIWebView/Sources/URLInjector/WebViewUrlComparator.swift b/TIWebView/Sources/URLInjector/WebViewUrlComparator.swift index 192c04f8..39545913 100644 --- a/TIWebView/Sources/URLInjector/WebViewUrlComparator.swift +++ b/TIWebView/Sources/URLInjector/WebViewUrlComparator.swift @@ -20,10 +20,12 @@ // THE SOFTWARE. // +import Foundation + public enum WebViewUrlComparator: Hashable { case any case absolutePath(String) case host(String) case query(String) - case regex(String) + case regex(NSRegularExpression) } diff --git a/TIWebView/Sources/URLInjector/WebViewUrlInjector.swift b/TIWebView/Sources/URLInjector/WebViewUrlInjector.swift new file mode 100644 index 00000000..89409e49 --- /dev/null +++ b/TIWebView/Sources/URLInjector/WebViewUrlInjector.swift @@ -0,0 +1,86 @@ +// +// 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 class WebKit.WKWebView + +public typealias URLInjection = [WebViewUrlComparator: [WebViewUrlInjection]] + +public protocol WebViewUrlInjector { + var injection: URLInjection { get set } + + func inject(into webView: WKWebView) +} + +public extension WebViewUrlInjector { + + func inject(into webView: WKWebView) { + guard !injection.isEmpty, let url = webView.url else { + return + } + + injection.forEach { (comparator, injections) in + guard url.compare(by: comparator) else { + return + } + + injections.forEach { evaluteInjection(onWebView: webView, injection: $0) } + } + } + + // MARK: - Helper methods + + private func evaluteInjection(onWebView webView: WKWebView, injection: WebViewUrlInjection) { + let jsScript = makeJsScript(fromInjection: injection) + + guard !jsScript.isEmpty else { + return + } + + webView.evaluateJavaScript(jsScript, completionHandler: nil) + } + + private func makeJsScript(fromInjection injection: WebViewUrlInjection) -> String { + switch injection { + case let .css(css): + return cssJsScript(css: css) + + case let .cssForFile(url): + let css = try? String(contentsOf: url) + .components(separatedBy: .newlines) + .joined() + + return cssJsScript(css: css ?? "") + + case let .javaScript(script): + return script + } + } + + private func cssJsScript(css: String) -> String { + """ + var style = document.createElement('style'); + style.innerHTML = '\(css)'; + document.head.appendChild(style); + """ + } +} diff --git a/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift b/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift index 4d62c64a..2883611a 100644 --- a/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift +++ b/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift @@ -24,15 +24,15 @@ import WebKit open class DefaultWebViewModel: NSObject, WebViewModel { - public var injector: BaseWebViewUrlInjector - public var navigator: BaseWebViewNavigator - public var errorHandler: BaseWebViewErrorHandler + public var injector: WebViewUrlInjector + public var navigator: WebViewNavigator + public var errorHandler: WebViewErrorHandler // MARK: - Init - public init(injector: BaseWebViewUrlInjector = .init(), - navigator: BaseWebViewNavigator = .init(), - errorHandler: BaseWebViewErrorHandler = .init()) { + public init(injector: WebViewUrlInjector = BaseWebViewUrlInjector(), + navigator: WebViewNavigator = BaseWebViewNavigator(), + errorHandler: WebViewErrorHandler = BaseWebViewErrorHandler()) { self.injector = injector self.navigator = navigator @@ -56,7 +56,9 @@ open class DefaultWebViewModel: NSObject, WebViewModel { private func parseError(_ message: WKScriptMessage) -> WebViewError { let body = message.body as? [String: Any] - let error = body?[WebViewErrorConstants.errorPropertyName] as? String - return .jsError(message.webView?.url, error ?? "") + return WebViewJSError(contentURL: body?[WebViewErrorConstants.errorUrl] as? URL, + name: body?[WebViewErrorConstants.errorName] as? String, + message: body?[WebViewErrorConstants.errorMessage] as? String, + stackTrace: body?[WebViewErrorConstants.errorStack] as? String) } } diff --git a/TIWebView/Sources/Views/ViewModels/WebViewModel.swift b/TIWebView/Sources/Views/ViewModels/WebViewModel.swift index 5250bc69..7894c78b 100644 --- a/TIWebView/Sources/Views/ViewModels/WebViewModel.swift +++ b/TIWebView/Sources/Views/ViewModels/WebViewModel.swift @@ -23,26 +23,27 @@ import WebKit public protocol WebViewModel: WKScriptMessageHandler { - var injector: BaseWebViewUrlInjector { get } - var navigator: BaseWebViewNavigator { get } - var errorHandler: BaseWebViewErrorHandler { get } + var injector: WebViewUrlInjector { get } + var navigator: WebViewNavigator { get } + var errorHandler: WebViewErrorHandler { get } - func makeUrlInjection(forWebView webView: WKWebView) - func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy + func makeUrlInjection(into webView: WKWebView) + func shouldNavigate(to url: URL) -> WKNavigationActionPolicy func handleError(_ error: Error, url: URL?) } public extension WebViewModel { - func makeUrlInjection(forWebView webView: WKWebView) { - injector.inject(on: webView) + func makeUrlInjection(into webView: WKWebView) { + injector.inject(into: webView) } - func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy { - navigator.shouldNavigate(toUrl: url) + func shouldNavigate(to url: URL) -> WKNavigationActionPolicy { + navigator.shouldNavigate(to: url) } func handleError(_ error: Error, url: URL?) { - errorHandler.didRecievedError(.standardError(url, error)) + let errorModel = WebViewLoadingError(contentURL: url, innerError: error) + errorHandler.didRecievedError(errorModel) } } From 3e7307424aed97d4687e016f3cdc198e9d618259 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Mon, 9 Jan 2023 15:29:41 +0300 Subject: [PATCH 06/12] fix: change types of parameters for js errors --- .../ErrorHandler/WebViewError/WebViewJSError.swift | 13 +++++++++---- .../Views/ViewModels/DefaultWebViewModel.swift | 4 ++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift index d30c673a..b81cf0ae 100644 --- a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift +++ b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift @@ -25,15 +25,20 @@ import Foundation public struct WebViewJSError: WebViewError, Codable { public let contentURL: URL? public let name: String? - public let message: String? + public let message: Int? public let stackTrace: String? - public init(contentURL: URL?, + public init(stringURL: String?, name: String?, - message: String?, + message: Int?, stackTrace: String?) { - self.contentURL = contentURL + if let stringURL = stringURL { + self.contentURL = URL(string: stringURL) + } else { + self.contentURL = nil + } + self.name = name self.message = message self.stackTrace = stackTrace diff --git a/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift b/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift index 2883611a..dadae799 100644 --- a/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift +++ b/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift @@ -56,9 +56,9 @@ open class DefaultWebViewModel: NSObject, WebViewModel { private func parseError(_ message: WKScriptMessage) -> WebViewError { let body = message.body as? [String: Any] - return WebViewJSError(contentURL: body?[WebViewErrorConstants.errorUrl] as? URL, + return WebViewJSError(stringURL: body?[WebViewErrorConstants.errorUrl] as? String, name: body?[WebViewErrorConstants.errorName] as? String, - message: body?[WebViewErrorConstants.errorMessage] as? String, + message: body?[WebViewErrorConstants.errorMessage] as? Int, stackTrace: body?[WebViewErrorConstants.errorStack] as? String) } } From e4c8118b6b418fe8d3d1131b63313e86c475d4be Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Tue, 10 Jan 2023 19:53:47 +0300 Subject: [PATCH 07/12] fix: code review notes --- .../WebViewError/WebViewJSError.swift | 16 ++++++++++++++-- .../ErrorHandler/WebViewErrorConstants.swift | 4 ++++ .../Sources/URLInjector/WebViewUrlInjector.swift | 12 +++++++----- .../Views/ViewModels/DefaultWebViewModel.swift | 3 ++- project-scripts/push_to_podspecs.sh | 3 ++- 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift index b81cf0ae..4fb04cda 100644 --- a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift +++ b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift @@ -23,14 +23,25 @@ import Foundation public struct WebViewJSError: WebViewError, Codable { + + public enum CodingKeys: String, CodingKey { + case contentURL + case name + case message + case lineNumber = "line" + case stackTrace = "stack" + } + public let contentURL: URL? public let name: String? - public let message: Int? + public let message: String? + public let lineNumber: Int? public let stackTrace: String? public init(stringURL: String?, name: String?, - message: Int?, + message: String?, + lineNumber: Int?, stackTrace: String?) { if let stringURL = stringURL { @@ -41,6 +52,7 @@ public struct WebViewJSError: WebViewError, Codable { self.name = name self.message = message + self.lineNumber = lineNumber self.stackTrace = stackTrace } } diff --git a/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift b/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift index 07d266e0..dc9c4745 100644 --- a/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift +++ b/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift @@ -29,6 +29,10 @@ public enum WebViewErrorConstants { "message" } + static var errorLineNumber: String { + "line" + } + static var errorName: String { "name" } diff --git a/TIWebView/Sources/URLInjector/WebViewUrlInjector.swift b/TIWebView/Sources/URLInjector/WebViewUrlInjector.swift index 89409e49..aa63fc6c 100644 --- a/TIWebView/Sources/URLInjector/WebViewUrlInjector.swift +++ b/TIWebView/Sources/URLInjector/WebViewUrlInjector.swift @@ -50,16 +50,14 @@ public extension WebViewUrlInjector { // MARK: - Helper methods private func evaluteInjection(onWebView webView: WKWebView, injection: WebViewUrlInjection) { - let jsScript = makeJsScript(fromInjection: injection) - - guard !jsScript.isEmpty else { + guard let jsScript = makeJsScript(fromInjection: injection) else { return } webView.evaluateJavaScript(jsScript, completionHandler: nil) } - private func makeJsScript(fromInjection injection: WebViewUrlInjection) -> String { + private func makeJsScript(fromInjection injection: WebViewUrlInjection) -> String? { switch injection { case let .css(css): return cssJsScript(css: css) @@ -69,7 +67,11 @@ public extension WebViewUrlInjector { .components(separatedBy: .newlines) .joined() - return cssJsScript(css: css ?? "") + if let css = css, !css.isEmpty { + return cssJsScript(css: css) + } + + return nil case let .javaScript(script): return script diff --git a/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift b/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift index dadae799..c633771e 100644 --- a/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift +++ b/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift @@ -58,7 +58,8 @@ open class DefaultWebViewModel: NSObject, WebViewModel { let body = message.body as? [String: Any] return WebViewJSError(stringURL: body?[WebViewErrorConstants.errorUrl] as? String, name: body?[WebViewErrorConstants.errorName] as? String, - message: body?[WebViewErrorConstants.errorMessage] as? Int, + message: body?[WebViewErrorConstants.errorMessage] as? String, + lineNumber: body?[WebViewErrorConstants.errorLineNumber] as? Int, stackTrace: body?[WebViewErrorConstants.errorStack] as? String) } } diff --git a/project-scripts/push_to_podspecs.sh b/project-scripts/push_to_podspecs.sh index 217d9687..c53eec9f 100755 --- a/project-scripts/push_to_podspecs.sh +++ b/project-scripts/push_to_podspecs.sh @@ -22,7 +22,8 @@ ORDERED_PODSPECS="../TISwiftUtils/TISwiftUtils.podspec ../TIAppleMapUtils/TIAppleMapUtils.podspec ../TIGoogleMapUtils/TIGoogleMapUtils.podspec ../TIYandexMapUtils/TIYandexMapUtils.podspec -../TIEcommerce/TIEcommerce.podspec" +../TIEcommerce/TIEcommerce.podspec +../TIWebView/TIWebView.podspec" for podspec_path in ${ORDERED_PODSPECS}; do bundle exec pod repo push git@github.com:TouchInstinct/Podspecs ${podspec_path} --allow-warnings From 47217f7a59af53f5ed5ac6aaf7a47e3627a46c94 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Tue, 10 Jan 2023 20:00:03 +0300 Subject: [PATCH 08/12] fix: paramenters for js error model to correspond to js exception models --- .../ErrorHandler/WebViewError/WebViewError.swift | 2 +- .../WebViewError/WebViewJSError.swift | 16 ++++++++++------ .../WebViewError/WebViewLoadingError.swift | 6 +++--- .../ErrorHandler/WebViewErrorConstants.swift | 6 +++++- .../Views/ViewModels/DefaultWebViewModel.swift | 3 ++- .../Sources/Views/ViewModels/WebViewModel.swift | 2 +- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewError.swift b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewError.swift index 1cc49cd1..451d2cdb 100644 --- a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewError.swift +++ b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewError.swift @@ -23,5 +23,5 @@ import Foundation public protocol WebViewError: Error { - var contentURL: URL? { get } + var sourceURL: URL? { get } } diff --git a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift index 4fb04cda..fd7bf336 100644 --- a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift +++ b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift @@ -25,34 +25,38 @@ import Foundation public struct WebViewJSError: WebViewError, Codable { public enum CodingKeys: String, CodingKey { - case contentURL + case sourceURL case name case message case lineNumber = "line" + case columnNumber = "column" case stackTrace = "stack" } - public let contentURL: URL? + public let sourceURL: URL? public let name: String? public let message: String? public let lineNumber: Int? + public let columnNumber: Int? public let stackTrace: String? - public init(stringURL: String?, + public init(sourceURL: String?, name: String?, message: String?, lineNumber: Int?, + columnNumber: Int?, stackTrace: String?) { - if let stringURL = stringURL { - self.contentURL = URL(string: stringURL) + if let sourceURL = sourceURL { + self.sourceURL = URL(string: sourceURL) } else { - self.contentURL = nil + self.sourceURL = nil } self.name = name self.message = message self.lineNumber = lineNumber + self.columnNumber = columnNumber self.stackTrace = stackTrace } } diff --git a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewLoadingError.swift b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewLoadingError.swift index 5f8a10c9..afbd5fb8 100644 --- a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewLoadingError.swift +++ b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewLoadingError.swift @@ -23,11 +23,11 @@ import Foundation public struct WebViewLoadingError: WebViewError { - public let contentURL: URL? + public let sourceURL: URL? public let innerError: Error - public init(contentURL: URL?, innerError: Error) { - self.contentURL = contentURL + public init(sourceURL: URL?, innerError: Error) { + self.sourceURL = sourceURL self.innerError = innerError } } diff --git a/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift b/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift index dc9c4745..54473f8b 100644 --- a/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift +++ b/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift @@ -33,12 +33,16 @@ public enum WebViewErrorConstants { "line" } + static var errorColumnNumber: String { + "column" + } + static var errorName: String { "name" } static var errorUrl: String { - "url" + "sourceURL" } static var errorStack: String { diff --git a/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift b/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift index c633771e..efdf33e6 100644 --- a/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift +++ b/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift @@ -56,10 +56,11 @@ open class DefaultWebViewModel: NSObject, WebViewModel { private func parseError(_ message: WKScriptMessage) -> WebViewError { let body = message.body as? [String: Any] - return WebViewJSError(stringURL: body?[WebViewErrorConstants.errorUrl] as? String, + return WebViewJSError(sourceURL: body?[WebViewErrorConstants.errorUrl] as? String, name: body?[WebViewErrorConstants.errorName] as? String, message: body?[WebViewErrorConstants.errorMessage] as? String, lineNumber: body?[WebViewErrorConstants.errorLineNumber] as? Int, + columnNumber: body?[WebViewErrorConstants.errorColumnNumber] as? Int, stackTrace: body?[WebViewErrorConstants.errorStack] as? String) } } diff --git a/TIWebView/Sources/Views/ViewModels/WebViewModel.swift b/TIWebView/Sources/Views/ViewModels/WebViewModel.swift index 7894c78b..9cd4336e 100644 --- a/TIWebView/Sources/Views/ViewModels/WebViewModel.swift +++ b/TIWebView/Sources/Views/ViewModels/WebViewModel.swift @@ -43,7 +43,7 @@ public extension WebViewModel { } func handleError(_ error: Error, url: URL?) { - let errorModel = WebViewLoadingError(contentURL: url, innerError: error) + let errorModel = WebViewLoadingError(sourceURL: url, innerError: error) errorHandler.didRecievedError(errorModel) } } From 3bab367ce26650a1f8ec5bd90f3f771e07f3ddb5 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Wed, 11 Jan 2023 14:19:21 +0300 Subject: [PATCH 09/12] fix: code review notes --- .../WebViewError/WebViewJSError.swift | 9 +++++++++ .../Views/BaseInitializableWebView.swift | 2 +- .../Views/ViewModels/DefaultWebViewModel.swift | 17 +++++++---------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift index fd7bf336..b28a2a28 100644 --- a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift +++ b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift @@ -59,4 +59,13 @@ public struct WebViewJSError: WebViewError, Codable { self.columnNumber = columnNumber self.stackTrace = stackTrace } + + public init?(from json: [String: Any]) { + guard let data = try? JSONSerialization.data(withJSONObject: json), + let error = try? JSONDecoder().decode(WebViewJSError.self, from: data) else { + return nil + } + + self = error + } } diff --git a/TIWebView/Sources/Views/BaseInitializableWebView.swift b/TIWebView/Sources/Views/BaseInitializableWebView.swift index e73ebdd8..4782cd1b 100644 --- a/TIWebView/Sources/Views/BaseInitializableWebView.swift +++ b/TIWebView/Sources/Views/BaseInitializableWebView.swift @@ -82,7 +82,7 @@ open class BaseInitializableWebView: WKWebView, // MARK: - Public methods - public func subscribe(onProgress: ParameterClosure? = nil) -> NSKeyValueObservation { + public func subscribe(onProgress: ParameterClosure?) -> NSKeyValueObservation { observe(\.estimatedProgress, options: [.new]) { webView, change in if webView.isLoading, let newValue = change.newValue { onProgress?(newValue) diff --git a/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift b/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift index efdf33e6..b772bbae 100644 --- a/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift +++ b/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift @@ -46,21 +46,18 @@ open class DefaultWebViewModel: NSObject, WebViewModel { open func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - if message.name == WebViewErrorConstants.errorMessageName { - let error = parseError(message) + if message.name == WebViewErrorConstants.errorMessageName, + let error = parseError(message) { errorHandler.didRecievedError(error) } } // MARK: - Private methods - private func parseError(_ message: WKScriptMessage) -> WebViewError { - let body = message.body as? [String: Any] - return WebViewJSError(sourceURL: body?[WebViewErrorConstants.errorUrl] as? String, - name: body?[WebViewErrorConstants.errorName] as? String, - message: body?[WebViewErrorConstants.errorMessage] as? String, - lineNumber: body?[WebViewErrorConstants.errorLineNumber] as? Int, - columnNumber: body?[WebViewErrorConstants.errorColumnNumber] as? Int, - stackTrace: body?[WebViewErrorConstants.errorStack] as? String) + private func parseError(_ message: WKScriptMessage) -> WebViewError? { + guard let body = message.body as? [String: Any] else { + return nil + } + return WebViewJSError(from: body) } } From 79a5ed414993756e9f3213d9391e8ad80a08455e Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Wed, 11 Jan 2023 14:35:19 +0300 Subject: [PATCH 10/12] fix: remove redundant constants --- .../ErrorHandler/WebViewErrorConstants.swift | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift b/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift index 54473f8b..b4ed3e94 100644 --- a/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift +++ b/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.swift @@ -24,28 +24,4 @@ public enum WebViewErrorConstants { static var errorMessageName: String { "error" } - - static var errorMessage: String { - "message" - } - - static var errorLineNumber: String { - "line" - } - - static var errorColumnNumber: String { - "column" - } - - static var errorName: String { - "name" - } - - static var errorUrl: String { - "sourceURL" - } - - static var errorStack: String { - "stack" - } } From 0204aa9b10931657a745696ba6269a654cc2682f Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Wed, 11 Jan 2023 14:46:37 +0300 Subject: [PATCH 11/12] feat: added json decoder parameter for js error model init --- .../Sources/ErrorHandler/WebViewError/WebViewJSError.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift index b28a2a28..807c06ff 100644 --- a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift +++ b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift @@ -60,9 +60,9 @@ public struct WebViewJSError: WebViewError, Codable { self.stackTrace = stackTrace } - public init?(from json: [String: Any]) { + public init?(from json: [String: Any], jsonDecoder: JSONDecoder = .init()) { guard let data = try? JSONSerialization.data(withJSONObject: json), - let error = try? JSONDecoder().decode(WebViewJSError.self, from: data) else { + let error = try? jsonDecoder.decode(WebViewJSError.self, from: data) else { return nil } From f6e00bb53a0eb6a20a4db84deb6accf39e598018 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Mon, 30 Jan 2023 11:42:06 +0300 Subject: [PATCH 12/12] fix: code review notes --- CHANGELOG.md | 2 +- TIWebView/Sources/ErrorHandler/WebViewErrorHandler.swift | 4 ++-- .../Sources/NavigationHandler/BaseWebViewNavigator.swift | 8 ++++---- .../Sources/NavigationHandler/WebViewNavigator.swift | 6 +++--- .../Sources/Views/ViewModels/DefaultWebViewModel.swift | 2 +- TIWebView/Sources/Views/ViewModels/WebViewModel.swift | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7df67573..01468992 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### 1.32.0 -- **Added**: `BaseInitializableWebView`with navigation and error handling api. +- **Added**: `BaseInitializableWebView` with navigation and error handling api. ### 1.31.0 diff --git a/TIWebView/Sources/ErrorHandler/WebViewErrorHandler.swift b/TIWebView/Sources/ErrorHandler/WebViewErrorHandler.swift index 97ace4df..7a58b748 100644 --- a/TIWebView/Sources/ErrorHandler/WebViewErrorHandler.swift +++ b/TIWebView/Sources/ErrorHandler/WebViewErrorHandler.swift @@ -21,11 +21,11 @@ // public protocol WebViewErrorHandler { - func didRecievedError(_ error: WebViewError) + func didReceiveError(_ error: WebViewError) } public extension WebViewErrorHandler { - func didRecievedError(_ error: WebViewError) { + func didReceiveError(_ error: WebViewError) { // override in subclasses } } diff --git a/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift b/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift index 57095021..b7eb828a 100644 --- a/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift +++ b/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift @@ -22,13 +22,13 @@ open class BaseWebViewNavigator: WebViewNavigator { - public var navigationMap: [NavigationPolicy] + public var navigationPolicyMap: [NavigationPolicy] - public init(navigationMap: [NavigationPolicy]) { - self.navigationMap = navigationMap + public init(navigationPolicyMap: [NavigationPolicy]) { + self.navigationPolicyMap = navigationPolicyMap } public convenience init() { - self.init(navigationMap: []) + self.init(navigationPolicyMap: []) } } diff --git a/TIWebView/Sources/NavigationHandler/WebViewNavigator.swift b/TIWebView/Sources/NavigationHandler/WebViewNavigator.swift index 303f7782..8fd653ce 100644 --- a/TIWebView/Sources/NavigationHandler/WebViewNavigator.swift +++ b/TIWebView/Sources/NavigationHandler/WebViewNavigator.swift @@ -24,18 +24,18 @@ import Foundation import enum WebKit.WKNavigationActionPolicy public protocol WebViewNavigator { - var navigationMap: [NavigationPolicy] { get set } + var navigationPolicyMap: [NavigationPolicy] { get set } func shouldNavigate(to url: URL) -> WKNavigationActionPolicy } public extension WebViewNavigator { func shouldNavigate(to url: URL) -> WKNavigationActionPolicy { - guard !navigationMap.isEmpty else { + guard !navigationPolicyMap.isEmpty else { return .cancel } - let allowPolicy = navigationMap.filter { $0.policy(for: url) == .allow } + let allowPolicy = navigationPolicyMap.filter { $0.policy(for: url) == .allow } return allowPolicy.isEmpty ? .cancel : .allow } } diff --git a/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift b/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift index b772bbae..6caaf1de 100644 --- a/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift +++ b/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift @@ -48,7 +48,7 @@ open class DefaultWebViewModel: NSObject, WebViewModel { if message.name == WebViewErrorConstants.errorMessageName, let error = parseError(message) { - errorHandler.didRecievedError(error) + errorHandler.didReceiveError(error) } } diff --git a/TIWebView/Sources/Views/ViewModels/WebViewModel.swift b/TIWebView/Sources/Views/ViewModels/WebViewModel.swift index 9cd4336e..409d21b4 100644 --- a/TIWebView/Sources/Views/ViewModels/WebViewModel.swift +++ b/TIWebView/Sources/Views/ViewModels/WebViewModel.swift @@ -44,6 +44,6 @@ public extension WebViewModel { func handleError(_ error: Error, url: URL?) { let errorModel = WebViewLoadingError(sourceURL: url, innerError: error) - errorHandler.didRecievedError(errorModel) + errorHandler.didReceiveError(errorModel) } }