diff --git a/CHANGELOG.md b/CHANGELOG.md index 547b2094..01468992 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### 1.32.0 + +- **Added**: `BaseInitializableWebView` with navigation and error handling api. + ### 1.31.0 - **Added**: `URLInteractiveTextView` for terms and conditions hints in login flow diff --git a/LeadKit.podspec b/LeadKit.podspec index 28681540..ea371335 100644 --- a/LeadKit.podspec +++ b/LeadKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "LeadKit" - s.version = "1.31.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/Package.swift b/Package.swift index 87950b8c..63305190 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"], path: "TIWebView/Sources"), // MARK: - SwiftUI .target(name: "TISwiftUICore", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TISwiftUICore/Sources"), diff --git a/TIAppleMapUtils/TIAppleMapUtils.podspec b/TIAppleMapUtils/TIAppleMapUtils.podspec index bb40275c..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.31.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 61653f55..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.31.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 37550a6c..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.31.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 bbac7668..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.31.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 187cac21..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.31.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 84abca4e..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.31.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 63ab9d9a..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.31.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 d55b1741..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.31.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 3f6c548d..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.31.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 1465c72c..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.31.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 3273b93d..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.31.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 3c5091ce..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.31.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 803f5ae1..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.31.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 8e5af467..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.31.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 b7e7787e..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.31.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 20a90230..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.31.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 f71b2b97..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.31.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 ccdab3b8..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.31.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/Sources/ErrorHandler/BaseWebViewErrorHandler.swift b/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift new file mode 100644 index 00000000..cbe68ac9 --- /dev/null +++ b/TIWebView/Sources/ErrorHandler/BaseWebViewErrorHandler.swift @@ -0,0 +1,28 @@ +// +// 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. +// + +open class BaseWebViewErrorHandler: WebViewErrorHandler { + + public init() { + + } +} diff --git a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewError.swift b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewError.swift new file mode 100644 index 00000000..451d2cdb --- /dev/null +++ b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewError.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. +// + +import Foundation + +public protocol WebViewError: Error { + var sourceURL: URL? { get } +} diff --git a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift new file mode 100644 index 00000000..807c06ff --- /dev/null +++ b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewJSError.swift @@ -0,0 +1,71 @@ +// +// 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 enum CodingKeys: String, CodingKey { + case sourceURL + case name + case message + case lineNumber = "line" + case columnNumber = "column" + case stackTrace = "stack" + } + + 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(sourceURL: String?, + name: String?, + message: String?, + lineNumber: Int?, + columnNumber: Int?, + stackTrace: String?) { + + if let sourceURL = sourceURL { + self.sourceURL = URL(string: sourceURL) + } else { + self.sourceURL = nil + } + + self.name = name + self.message = message + self.lineNumber = lineNumber + self.columnNumber = columnNumber + self.stackTrace = stackTrace + } + + 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 { + return nil + } + + self = error + } +} diff --git a/TIWebView/Sources/ErrorHandler/WebViewError/WebViewLoadingError.swift b/TIWebView/Sources/ErrorHandler/WebViewError/WebViewLoadingError.swift new file mode 100644 index 00000000..afbd5fb8 --- /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 sourceURL: URL? + public let innerError: Error + + 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 new file mode 100644 index 00000000..b4ed3e94 --- /dev/null +++ b/TIWebView/Sources/ErrorHandler/WebViewErrorConstants.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 WebViewErrorConstants { + static var errorMessageName: String { + "error" + } +} diff --git a/TIWebView/Sources/ErrorHandler/WebViewErrorHandler.swift b/TIWebView/Sources/ErrorHandler/WebViewErrorHandler.swift new file mode 100644 index 00000000..7a58b748 --- /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 didReceiveError(_ error: WebViewError) +} + +public extension WebViewErrorHandler { + func didReceiveError(_ error: WebViewError) { + // override in subclasses + } +} diff --git a/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift b/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift new file mode 100644 index 00000000..b7eb828a --- /dev/null +++ b/TIWebView/Sources/NavigationHandler/BaseWebViewNavigator.swift @@ -0,0 +1,34 @@ +// +// 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. +// + +open class BaseWebViewNavigator: WebViewNavigator { + + public var navigationPolicyMap: [NavigationPolicy] + + public init(navigationPolicyMap: [NavigationPolicy]) { + self.navigationPolicyMap = navigationPolicyMap + } + + public convenience init() { + self.init(navigationPolicyMap: []) + } +} diff --git a/TIWebView/Sources/NavigationHandler/Helpers/URL+Validation.swift b/TIWebView/Sources/NavigationHandler/Helpers/URL+Validation.swift new file mode 100644 index 00000000..bcbe76ca --- /dev/null +++ b/TIWebView/Sources/NavigationHandler/Helpers/URL+Validation.swift @@ -0,0 +1,46 @@ +// +// 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 matches(_ 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): + if let urlQuery = self.query { + return urlQuery.contains(query) + } + return false + } + } +} 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/AlwaysAllowNavigationPolicy.swift b/TIWebView/Sources/NavigationHandler/NavigationPolicy/AlwaysAllowNavigationPolicy.swift new file mode 100644 index 00000000..4916580b --- /dev/null +++ b/TIWebView/Sources/NavigationHandler/NavigationPolicy/AlwaysAllowNavigationPolicy.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 AlwaysAllowNavigationPolicy: NavigationPolicy { + + public init() { + + } + + open func policy(for url: URL) -> WKNavigationActionPolicy { + .allow + } +} diff --git a/TIWebView/Sources/NavigationHandler/NavigationPolicy/NavigationPolicy.swift b/TIWebView/Sources/NavigationHandler/NavigationPolicy/NavigationPolicy.swift new file mode 100644 index 00000000..57b013d4 --- /dev/null +++ b/TIWebView/Sources/NavigationHandler/NavigationPolicy/NavigationPolicy.swift @@ -0,0 +1,28 @@ +// +// 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 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..40d9a8c7 --- /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: AlwaysAllowNavigationPolicy { + + 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.matches(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..568ddb11 --- /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: AlwaysAllowNavigationPolicy { + + 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/NavigationHandler/WebViewNavigator.swift b/TIWebView/Sources/NavigationHandler/WebViewNavigator.swift new file mode 100644 index 00000000..8fd653ce --- /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 navigationPolicyMap: [NavigationPolicy] { get set } + + func shouldNavigate(to url: URL) -> WKNavigationActionPolicy +} + +public extension WebViewNavigator { + func shouldNavigate(to url: URL) -> WKNavigationActionPolicy { + guard !navigationPolicyMap.isEmpty else { + return .cancel + } + + let allowPolicy = navigationPolicyMap.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 new file mode 100644 index 00000000..6cd66fae --- /dev/null +++ b/TIWebView/Sources/StateDelegate/BaseWebViewStateHandler.swift @@ -0,0 +1,64 @@ +// +// 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: WebViewModel? + + // MARK: - WebViewStateHandler + + 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(to: url) ?? .cancel + decisionHandler(decision) + } + + open func webView(_ webView: WKWebView, didCommit navigation: WKNavigation?) { + // override in subclasses + } + + open func webView(_ webView: WKWebView, didFinish navigation: WKNavigation?) { + viewModel?.makeUrlInjection(into: 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/StateDelegate/WebViewStateHandler.swift b/TIWebView/Sources/StateDelegate/WebViewStateHandler.swift new file mode 100644 index 00000000..6d084f15 --- /dev/null +++ b/TIWebView/Sources/StateDelegate/WebViewStateHandler.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. +// + +import protocol WebKit.WKNavigationDelegate + +public protocol WebViewStateHandler: WKNavigationDelegate { + var viewModel: WebViewModel? { get set } +} diff --git a/TIWebView/Sources/URLInjector/BaseWebViewUrlInjector.swift b/TIWebView/Sources/URLInjector/BaseWebViewUrlInjector.swift new file mode 100644 index 00000000..2429a2d7 --- /dev/null +++ b/TIWebView/Sources/URLInjector/BaseWebViewUrlInjector.swift @@ -0,0 +1,37 @@ +// +// 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 + +open class BaseWebViewUrlInjector: WebViewUrlInjector { + + public var injection: URLInjection + + public init(injection: URLInjection) { + self.injection = injection + } + + public convenience init() { + self.init(injection: [:]) + } +} diff --git a/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift b/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.swift new file mode 100644 index 00000000..6c32c718 --- /dev/null +++ b/TIWebView/Sources/URLInjector/Helpers/URL+Comparator.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 + +public extension URL { + func compare(by comparator: WebViewUrlComparator) -> Bool { + switch comparator { + case .any: + return true + + case let .absolutePath(path): + return compare(by: .absolutePath(path)) + + case let .host(host): + return compare(by: .host(host)) + + case let .query(query): + return compare(by: .query(query)) + + case let .regex(nsRegex): + return matches(nsRegex) + } + } +} diff --git a/TIWebView/Sources/URLInjector/WebViewUrlComparator.swift b/TIWebView/Sources/URLInjector/WebViewUrlComparator.swift new file mode 100644 index 00000000..39545913 --- /dev/null +++ b/TIWebView/Sources/URLInjector/WebViewUrlComparator.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. +// + +import Foundation + +public enum WebViewUrlComparator: Hashable { + case any + case absolutePath(String) + case host(String) + case query(String) + case regex(NSRegularExpression) +} diff --git a/TIWebView/Sources/URLInjector/WebViewUrlInjection.swift b/TIWebView/Sources/URLInjector/WebViewUrlInjection.swift new file mode 100644 index 00000000..d2c7f228 --- /dev/null +++ b/TIWebView/Sources/URLInjector/WebViewUrlInjection.swift @@ -0,0 +1,29 @@ +// +// 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 enum WebViewUrlInjection { + case css(String) + case cssForFile(URL) + case javaScript(String) +} diff --git a/TIWebView/Sources/URLInjector/WebViewUrlInjector.swift b/TIWebView/Sources/URLInjector/WebViewUrlInjector.swift new file mode 100644 index 00000000..aa63fc6c --- /dev/null +++ b/TIWebView/Sources/URLInjector/WebViewUrlInjector.swift @@ -0,0 +1,88 @@ +// +// 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) { + guard let jsScript = makeJsScript(fromInjection: injection) 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() + + if let css = css, !css.isEmpty { + return cssJsScript(css: css) + } + + return nil + + 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/BaseInitializableWebView.swift b/TIWebView/Sources/Views/BaseInitializableWebView.swift new file mode 100644 index 00000000..4782cd1b --- /dev/null +++ b/TIWebView/Sources/Views/BaseInitializableWebView.swift @@ -0,0 +1,92 @@ +// +// 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 TISwiftUtils +import TIUIKitCore +import WebKit + +open class BaseInitializableWebView: WKWebView, + InitializableViewProtocol, + ConfigurableView { + + public var stateHandler: WebViewStateHandler + public var viewModel: WebViewModel? { + didSet { + stateHandler.viewModel = viewModel + } + } + + // MARK: - Init + + public init(stateHandler: WebViewStateHandler = BaseWebViewStateHandler()) { + self.stateHandler = stateHandler + + 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 = stateHandler + } + + public func configureAppearance() { + // override in subviews + } + + public func localize() { + // override in subviews + } + + // MARK: - ConfigurableView + + open func configure(with viewModel: WebViewModel) { + self.viewModel = viewModel + + configuration.userContentController.add(viewModel, name: WebViewErrorConstants.errorMessageName) + } + + // MARK: - Public methods + + 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 new file mode 100644 index 00000000..6caaf1de --- /dev/null +++ b/TIWebView/Sources/Views/ViewModels/DefaultWebViewModel.swift @@ -0,0 +1,63 @@ +// +// 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 DefaultWebViewModel: NSObject, WebViewModel { + + public var injector: WebViewUrlInjector + public var navigator: WebViewNavigator + public var errorHandler: WebViewErrorHandler + + // MARK: - Init + + public init(injector: WebViewUrlInjector = BaseWebViewUrlInjector(), + navigator: WebViewNavigator = BaseWebViewNavigator(), + errorHandler: WebViewErrorHandler = BaseWebViewErrorHandler()) { + + self.injector = injector + self.navigator = navigator + self.errorHandler = errorHandler + + super.init() + } + + // MARK: - WKScriptMessageHandler + + open func userContentController(_ userContentController: WKUserContentController, + didReceive message: WKScriptMessage) { + + if message.name == WebViewErrorConstants.errorMessageName, + let error = parseError(message) { + errorHandler.didReceiveError(error) + } + } + + // MARK: - Private methods + + private func parseError(_ message: WKScriptMessage) -> WebViewError? { + guard let body = message.body as? [String: Any] else { + return nil + } + return WebViewJSError(from: body) + } +} diff --git a/TIWebView/Sources/Views/ViewModels/WebViewModel.swift b/TIWebView/Sources/Views/ViewModels/WebViewModel.swift new file mode 100644 index 00000000..409d21b4 --- /dev/null +++ b/TIWebView/Sources/Views/ViewModels/WebViewModel.swift @@ -0,0 +1,49 @@ +// +// 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 + +public protocol WebViewModel: WKScriptMessageHandler { + var injector: WebViewUrlInjector { get } + var navigator: WebViewNavigator { get } + var errorHandler: WebViewErrorHandler { get } + + func makeUrlInjection(into webView: WKWebView) + func shouldNavigate(to url: URL) -> WKNavigationActionPolicy + func handleError(_ error: Error, url: URL?) +} + +public extension WebViewModel { + + func makeUrlInjection(into webView: WKWebView) { + injector.inject(into: webView) + } + + func shouldNavigate(to url: URL) -> WKNavigationActionPolicy { + navigator.shouldNavigate(to: url) + } + + func handleError(_ error: Error, url: URL?) { + let errorModel = WebViewLoadingError(sourceURL: url, innerError: error) + errorHandler.didReceiveError(errorModel) + } +} diff --git a/TIWebView/TIWebView.podspec b/TIWebView/TIWebView.podspec new file mode 100644 index 00000000..aca8bb71 --- /dev/null +++ b/TIWebView/TIWebView.podspec @@ -0,0 +1,19 @@ +Pod::Spec.new do |s| + s.name = 'TIWebView' + 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' } + 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 = '11.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 + +end diff --git a/TIYandexMapUtils/TIYandexMapUtils.podspec b/TIYandexMapUtils/TIYandexMapUtils.podspec index 7ba1b0ea..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.31.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' } 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