@capawesome/capacitor-in-app-browser 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapawesomeCapacitorInAppBrowser.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +28 -0
- package/README.md +690 -0
- package/android/build.gradle +60 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/InAppBrowser.java +242 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/InAppBrowserPlugin.java +289 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/classes/CustomException.java +20 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/classes/CustomExceptions.java +20 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/classes/WebViewDialog.java +343 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/classes/events/BrowserPageNavigationCompletedEvent.java +23 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/classes/events/MessageReceivedEvent.java +23 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/classes/options/ExecuteScriptOptions.java +29 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/classes/options/GetCookiesOptions.java +29 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/classes/options/OpenInExternalBrowserOptions.java +30 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/classes/options/OpenInSystemBrowserOptions.java +74 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/classes/options/OpenInWebViewOptions.java +112 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/classes/options/PostMessageOptions.java +30 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/classes/options/WebViewToolbarOptions.java +87 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/classes/results/ExecuteScriptResult.java +25 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/classes/results/GetCookiesResult.java +28 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/interfaces/Callback.java +5 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/interfaces/EmptyCallback.java +5 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/interfaces/NonEmptyResultCallback.java +7 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/inappbrowser/interfaces/Result.java +7 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +1173 -0
- package/dist/esm/definitions.d.ts +540 -0
- package/dist/esm/definitions.js +19 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +14 -0
- package/dist/esm/web.js +35 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +68 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +71 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/Classes/Events/BrowserPageNavigationCompletedEvent.swift +16 -0
- package/ios/Plugin/Classes/Events/MessageReceivedEvent.swift +24 -0
- package/ios/Plugin/Classes/Options/ExecuteScriptOptions.swift +17 -0
- package/ios/Plugin/Classes/Options/GetCookiesOptions.swift +20 -0
- package/ios/Plugin/Classes/Options/OpenInExternalBrowserOptions.swift +20 -0
- package/ios/Plugin/Classes/Options/OpenInSystemBrowserOptions.swift +52 -0
- package/ios/Plugin/Classes/Options/OpenInWebViewOptions.swift +48 -0
- package/ios/Plugin/Classes/Options/PostMessageOptions.swift +17 -0
- package/ios/Plugin/Classes/Options/WebViewToolbarOptions.swift +37 -0
- package/ios/Plugin/Classes/Results/ExecuteScriptResult.swift +25 -0
- package/ios/Plugin/Classes/Results/GetCookiesResult.swift +20 -0
- package/ios/Plugin/Classes/WebViewController.swift +224 -0
- package/ios/Plugin/Enums/CustomError.swift +49 -0
- package/ios/Plugin/InAppBrowser.swift +194 -0
- package/ios/Plugin/InAppBrowserHelper.swift +49 -0
- package/ios/Plugin/InAppBrowserPlugin.swift +191 -0
- package/ios/Plugin/Info.plist +24 -0
- package/ios/Plugin/Protocols/Result.swift +5 -0
- package/package.json +91 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import WebKit
|
|
4
|
+
|
|
5
|
+
@objc public class WebViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate {
|
|
6
|
+
var onClosed: ((WebViewController) -> Void)?
|
|
7
|
+
var onMessageReceived: ((String) -> Void)?
|
|
8
|
+
var onPageLoaded: (() -> Void)?
|
|
9
|
+
var onPageNavigationCompleted: ((String) -> Void)?
|
|
10
|
+
|
|
11
|
+
private static let bridgeJavaScript =
|
|
12
|
+
"window.CapacitorInAppBrowser = window.CapacitorInAppBrowser || { postMessage: function (data) "
|
|
13
|
+
+ "{ window.webkit.messageHandlers.capacitorInAppBrowser.postMessage(JSON.stringify(data)); } };"
|
|
14
|
+
private static let messageHandlerName = "capacitorInAppBrowser"
|
|
15
|
+
|
|
16
|
+
private let options: OpenInWebViewOptions
|
|
17
|
+
|
|
18
|
+
private var backButtonItem: UIBarButtonItem?
|
|
19
|
+
private var canGoBackObservation: NSKeyValueObservation?
|
|
20
|
+
private var canGoForwardObservation: NSKeyValueObservation?
|
|
21
|
+
private var forwardButtonItem: UIBarButtonItem?
|
|
22
|
+
private var initialLoadNotified = false
|
|
23
|
+
private var titleObservation: NSKeyValueObservation?
|
|
24
|
+
private var urlObservation: NSKeyValueObservation?
|
|
25
|
+
private var webView: WKWebView?
|
|
26
|
+
|
|
27
|
+
init(options: OpenInWebViewOptions) {
|
|
28
|
+
self.options = options
|
|
29
|
+
super.init(nibName: nil, bundle: nil)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
required init?(coder: NSCoder) {
|
|
33
|
+
fatalError("init(coder:) has not been implemented")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public func executeScript(_ script: String, completion: @escaping (_ result: Any?, _ error: Error?) -> Void) {
|
|
37
|
+
guard let webView = webView else {
|
|
38
|
+
completion(nil, nil)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
webView.evaluateJavaScript(script) { result, error in
|
|
42
|
+
completion(result, error)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public func postMessage(_ data: String) {
|
|
47
|
+
webView?.evaluateJavaScript(
|
|
48
|
+
"window.dispatchEvent(new CustomEvent('capacitorInAppBrowserMessage', { detail: \(data) }));",
|
|
49
|
+
completionHandler: nil
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
|
54
|
+
if let data = message.body as? String {
|
|
55
|
+
onMessageReceived?(data)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
override public func viewDidDisappear(_ animated: Bool) {
|
|
60
|
+
super.viewDidDisappear(animated)
|
|
61
|
+
if isBeingDismissed || navigationController?.isBeingDismissed == true {
|
|
62
|
+
cleanup()
|
|
63
|
+
onClosed?(self)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override public func viewDidLoad() {
|
|
68
|
+
super.viewDidLoad()
|
|
69
|
+
view.backgroundColor = .systemBackground
|
|
70
|
+
let webView = createWebView()
|
|
71
|
+
self.webView = webView
|
|
72
|
+
view.addSubview(webView)
|
|
73
|
+
NSLayoutConstraint.activate([
|
|
74
|
+
webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
|
|
75
|
+
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
76
|
+
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
77
|
+
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
|
78
|
+
])
|
|
79
|
+
configureNavigationBar()
|
|
80
|
+
loadUrl()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
|
|
84
|
+
if navigationAction.targetFrame == nil {
|
|
85
|
+
webView.load(navigationAction.request)
|
|
86
|
+
}
|
|
87
|
+
return nil
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
91
|
+
updateNavigationButtons()
|
|
92
|
+
updateTitle()
|
|
93
|
+
if !initialLoadNotified {
|
|
94
|
+
initialLoadNotified = true
|
|
95
|
+
onPageLoaded?()
|
|
96
|
+
}
|
|
97
|
+
if let url = webView.url?.absoluteString {
|
|
98
|
+
onPageNavigationCompleted?(url)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) {
|
|
103
|
+
decisionHandler(.grant)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private func cleanup() {
|
|
107
|
+
webView?.configuration.userContentController.removeScriptMessageHandler(forName: Self.messageHandlerName)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private func configureNavigationBar() {
|
|
111
|
+
let closeButtonItem = UIBarButtonItem(
|
|
112
|
+
title: options.toolbar.closeButtonText,
|
|
113
|
+
style: .plain,
|
|
114
|
+
target: self,
|
|
115
|
+
action: #selector(handleClose)
|
|
116
|
+
)
|
|
117
|
+
navigationItem.leftBarButtonItem = closeButtonItem
|
|
118
|
+
if options.toolbar.showNavigationButtons {
|
|
119
|
+
let backButtonItem = UIBarButtonItem(
|
|
120
|
+
image: UIImage(systemName: "chevron.left"),
|
|
121
|
+
style: .plain,
|
|
122
|
+
target: self,
|
|
123
|
+
action: #selector(handleGoBack)
|
|
124
|
+
)
|
|
125
|
+
let forwardButtonItem = UIBarButtonItem(
|
|
126
|
+
image: UIImage(systemName: "chevron.right"),
|
|
127
|
+
style: .plain,
|
|
128
|
+
target: self,
|
|
129
|
+
action: #selector(handleGoForward)
|
|
130
|
+
)
|
|
131
|
+
navigationItem.rightBarButtonItems = [forwardButtonItem, backButtonItem]
|
|
132
|
+
self.backButtonItem = backButtonItem
|
|
133
|
+
self.forwardButtonItem = forwardButtonItem
|
|
134
|
+
updateNavigationButtons()
|
|
135
|
+
}
|
|
136
|
+
if let backgroundColor = options.toolbar.backgroundColor {
|
|
137
|
+
let appearance = UINavigationBarAppearance()
|
|
138
|
+
appearance.configureWithOpaqueBackground()
|
|
139
|
+
appearance.backgroundColor = backgroundColor
|
|
140
|
+
if let color = options.toolbar.color {
|
|
141
|
+
appearance.titleTextAttributes = [.foregroundColor: color]
|
|
142
|
+
}
|
|
143
|
+
navigationController?.navigationBar.standardAppearance = appearance
|
|
144
|
+
navigationController?.navigationBar.scrollEdgeAppearance = appearance
|
|
145
|
+
navigationController?.navigationBar.compactAppearance = appearance
|
|
146
|
+
}
|
|
147
|
+
if let color = options.toolbar.color {
|
|
148
|
+
navigationController?.navigationBar.tintColor = color
|
|
149
|
+
}
|
|
150
|
+
updateTitle()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private func createWebView() -> WKWebView {
|
|
154
|
+
let configuration = WKWebViewConfiguration()
|
|
155
|
+
configuration.allowsInlineMediaPlayback = true
|
|
156
|
+
configuration.mediaTypesRequiringUserActionForPlayback = options.mediaPlaybackRequiresUserAction ? .all : []
|
|
157
|
+
configuration.websiteDataStore = options.isolatedDataStore ? .nonPersistent() : .default()
|
|
158
|
+
let userContentController = WKUserContentController()
|
|
159
|
+
userContentController.addUserScript(WKUserScript(source: Self.bridgeJavaScript, injectionTime: .atDocumentStart, forMainFrameOnly: true))
|
|
160
|
+
userContentController.add(self, name: Self.messageHandlerName)
|
|
161
|
+
configuration.userContentController = userContentController
|
|
162
|
+
let webView = WKWebView(frame: .zero, configuration: configuration)
|
|
163
|
+
webView.translatesAutoresizingMaskIntoConstraints = false
|
|
164
|
+
webView.navigationDelegate = self
|
|
165
|
+
webView.uiDelegate = self
|
|
166
|
+
webView.allowsBackForwardNavigationGestures = options.allowsBackForwardNavigationGestures
|
|
167
|
+
webView.scrollView.bounces = options.overscroll
|
|
168
|
+
if let userAgent = options.userAgent {
|
|
169
|
+
webView.customUserAgent = userAgent
|
|
170
|
+
}
|
|
171
|
+
canGoBackObservation = webView.observe(\.canGoBack, options: [.new]) { [weak self] _, _ in
|
|
172
|
+
self?.updateNavigationButtons()
|
|
173
|
+
}
|
|
174
|
+
canGoForwardObservation = webView.observe(\.canGoForward, options: [.new]) { [weak self] _, _ in
|
|
175
|
+
self?.updateNavigationButtons()
|
|
176
|
+
}
|
|
177
|
+
titleObservation = webView.observe(\.title, options: [.new]) { [weak self] _, _ in
|
|
178
|
+
self?.updateTitle()
|
|
179
|
+
}
|
|
180
|
+
urlObservation = webView.observe(\.url, options: [.new]) { [weak self] _, _ in
|
|
181
|
+
self?.updateTitle()
|
|
182
|
+
}
|
|
183
|
+
return webView
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@objc private func handleClose() {
|
|
187
|
+
dismiss(animated: true)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@objc private func handleGoBack() {
|
|
191
|
+
if webView?.canGoBack == true {
|
|
192
|
+
webView?.goBack()
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@objc private func handleGoForward() {
|
|
197
|
+
if webView?.canGoForward == true {
|
|
198
|
+
webView?.goForward()
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private func loadUrl() {
|
|
203
|
+
var request = URLRequest(url: options.url)
|
|
204
|
+
for (key, value) in options.headers {
|
|
205
|
+
request.setValue(value, forHTTPHeaderField: key)
|
|
206
|
+
}
|
|
207
|
+
webView?.load(request)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private func updateNavigationButtons() {
|
|
211
|
+
backButtonItem?.isEnabled = webView?.canGoBack == true
|
|
212
|
+
forwardButtonItem?.isEnabled = webView?.canGoForward == true
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private func updateTitle() {
|
|
216
|
+
if let title = options.toolbar.title {
|
|
217
|
+
navigationItem.title = title
|
|
218
|
+
} else if options.toolbar.showUrl {
|
|
219
|
+
navigationItem.title = webView?.url?.absoluteString
|
|
220
|
+
} else {
|
|
221
|
+
navigationItem.title = webView?.title
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
enum CustomError: Error {
|
|
4
|
+
case backgroundColorInvalid
|
|
5
|
+
case browserNotFound
|
|
6
|
+
case colorInvalid
|
|
7
|
+
case dataMissing
|
|
8
|
+
case noBrowserOpen
|
|
9
|
+
case scriptMissing
|
|
10
|
+
case toolbarColorInvalid
|
|
11
|
+
case urlInvalid
|
|
12
|
+
case urlMissing
|
|
13
|
+
|
|
14
|
+
var code: String? {
|
|
15
|
+
switch self {
|
|
16
|
+
case .browserNotFound:
|
|
17
|
+
return "BROWSER_NOT_FOUND"
|
|
18
|
+
case .noBrowserOpen:
|
|
19
|
+
return "NO_BROWSER_OPEN"
|
|
20
|
+
default:
|
|
21
|
+
return nil
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
extension CustomError: LocalizedError {
|
|
27
|
+
public var errorDescription: String? {
|
|
28
|
+
switch self {
|
|
29
|
+
case .backgroundColorInvalid:
|
|
30
|
+
return NSLocalizedString("backgroundColor must be a valid hex color code.", comment: "backgroundColorInvalid")
|
|
31
|
+
case .browserNotFound:
|
|
32
|
+
return NSLocalizedString("No app was found on the device that can open the URL.", comment: "browserNotFound")
|
|
33
|
+
case .colorInvalid:
|
|
34
|
+
return NSLocalizedString("color must be a valid hex color code.", comment: "colorInvalid")
|
|
35
|
+
case .dataMissing:
|
|
36
|
+
return NSLocalizedString("data must be provided.", comment: "dataMissing")
|
|
37
|
+
case .noBrowserOpen:
|
|
38
|
+
return NSLocalizedString("No browser is currently open.", comment: "noBrowserOpen")
|
|
39
|
+
case .scriptMissing:
|
|
40
|
+
return NSLocalizedString("script must be provided.", comment: "scriptMissing")
|
|
41
|
+
case .toolbarColorInvalid:
|
|
42
|
+
return NSLocalizedString("toolbarColor must be a valid hex color code.", comment: "toolbarColorInvalid")
|
|
43
|
+
case .urlInvalid:
|
|
44
|
+
return NSLocalizedString("url is invalid.", comment: "urlInvalid")
|
|
45
|
+
case .urlMissing:
|
|
46
|
+
return NSLocalizedString("url must be provided.", comment: "urlMissing")
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
import SafariServices
|
|
4
|
+
import UIKit
|
|
5
|
+
import WebKit
|
|
6
|
+
|
|
7
|
+
@objc public class InAppBrowser: NSObject, SFSafariViewControllerDelegate {
|
|
8
|
+
let plugin: InAppBrowserPlugin
|
|
9
|
+
|
|
10
|
+
private var safariViewController: SFSafariViewController?
|
|
11
|
+
private var webViewController: WebViewController?
|
|
12
|
+
|
|
13
|
+
init(plugin: InAppBrowserPlugin) {
|
|
14
|
+
self.plugin = plugin
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@objc public func clearCache(completion: @escaping (_ error: Error?) -> Void) {
|
|
18
|
+
DispatchQueue.main.async {
|
|
19
|
+
let types: Set<String> = [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache]
|
|
20
|
+
WKWebsiteDataStore.default().removeData(ofTypes: types, modifiedSince: .distantPast) {
|
|
21
|
+
completion(nil)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@objc public func clearSessionData(completion: @escaping (_ error: Error?) -> Void) {
|
|
27
|
+
DispatchQueue.main.async {
|
|
28
|
+
let types: Set<String> = [WKWebsiteDataTypeCookies, WKWebsiteDataTypeLocalStorage, WKWebsiteDataTypeSessionStorage]
|
|
29
|
+
WKWebsiteDataStore.default().removeData(ofTypes: types, modifiedSince: .distantPast) {
|
|
30
|
+
completion(nil)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@objc public func close(completion: @escaping (_ error: Error?) -> Void) {
|
|
36
|
+
DispatchQueue.main.async {
|
|
37
|
+
if let webViewController = self.webViewController {
|
|
38
|
+
webViewController.dismiss(animated: true) {
|
|
39
|
+
completion(nil)
|
|
40
|
+
}
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
if let safariViewController = self.safariViewController {
|
|
44
|
+
safariViewController.dismiss(animated: true) {
|
|
45
|
+
self.safariViewController = nil
|
|
46
|
+
self.plugin.notifyBrowserClosedListeners()
|
|
47
|
+
completion(nil)
|
|
48
|
+
}
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
completion(CustomError.noBrowserOpen)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@objc public func executeScript(_ options: ExecuteScriptOptions, completion: @escaping (_ result: ExecuteScriptResult?, _ error: Error?) -> Void) {
|
|
56
|
+
DispatchQueue.main.async {
|
|
57
|
+
guard let webViewController = self.webViewController else {
|
|
58
|
+
completion(nil, CustomError.noBrowserOpen)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
webViewController.executeScript(options.script) { result, _ in
|
|
62
|
+
completion(ExecuteScriptResult(value: result), nil)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@objc public func getCookies(_ options: GetCookiesOptions, completion: @escaping (_ result: GetCookiesResult?, _ error: Error?) -> Void) {
|
|
68
|
+
DispatchQueue.main.async {
|
|
69
|
+
WKWebsiteDataStore.default().httpCookieStore.getAllCookies { cookies in
|
|
70
|
+
var result: [String: String] = [:]
|
|
71
|
+
for cookie in cookies where InAppBrowserHelper.isCookie(cookie, matchingURL: options.url) {
|
|
72
|
+
result[cookie.name] = cookie.value
|
|
73
|
+
}
|
|
74
|
+
completion(GetCookiesResult(cookies: result), nil)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@objc public func openInExternalBrowser(_ options: OpenInExternalBrowserOptions, completion: @escaping (_ error: Error?) -> Void) {
|
|
80
|
+
DispatchQueue.main.async {
|
|
81
|
+
UIApplication.shared.open(options.url, options: [:]) { success in
|
|
82
|
+
completion(success ? nil : CustomError.browserNotFound)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@objc public func openInSystemBrowser(_ options: OpenInSystemBrowserOptions, completion: @escaping (_ error: Error?) -> Void) {
|
|
88
|
+
DispatchQueue.main.async {
|
|
89
|
+
self.dismissActiveBrowser {
|
|
90
|
+
guard let bridgeViewController = self.plugin.bridge?.viewController else {
|
|
91
|
+
completion(CustomError.noBrowserOpen)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
let configuration = SFSafariViewController.Configuration()
|
|
95
|
+
configuration.barCollapsingEnabled = options.barCollapsing
|
|
96
|
+
configuration.entersReaderIfAvailable = options.readerMode
|
|
97
|
+
let safariViewController = SFSafariViewController(url: options.url, configuration: configuration)
|
|
98
|
+
safariViewController.delegate = self
|
|
99
|
+
safariViewController.dismissButtonStyle = options.dismissButtonStyle
|
|
100
|
+
if let toolbarColor = options.toolbarColor {
|
|
101
|
+
safariViewController.preferredBarTintColor = toolbarColor
|
|
102
|
+
}
|
|
103
|
+
self.safariViewController = safariViewController
|
|
104
|
+
bridgeViewController.present(safariViewController, animated: true) {
|
|
105
|
+
completion(nil)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@objc public func openInWebView(_ options: OpenInWebViewOptions, completion: @escaping (_ error: Error?) -> Void) {
|
|
112
|
+
DispatchQueue.main.async {
|
|
113
|
+
self.dismissActiveBrowser {
|
|
114
|
+
guard let bridgeViewController = self.plugin.bridge?.viewController else {
|
|
115
|
+
completion(CustomError.noBrowserOpen)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
let webViewController = WebViewController(options: options)
|
|
119
|
+
webViewController.onClosed = { [weak self] viewController in
|
|
120
|
+
guard let self = self else {
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
if self.webViewController === viewController {
|
|
124
|
+
self.webViewController = nil
|
|
125
|
+
}
|
|
126
|
+
self.plugin.notifyBrowserClosedListeners()
|
|
127
|
+
}
|
|
128
|
+
webViewController.onMessageReceived = { [weak self] data in
|
|
129
|
+
self?.handleMessageReceived(data)
|
|
130
|
+
}
|
|
131
|
+
webViewController.onPageLoaded = { [weak self] in
|
|
132
|
+
self?.plugin.notifyBrowserPageLoadedListeners()
|
|
133
|
+
}
|
|
134
|
+
webViewController.onPageNavigationCompleted = { [weak self] url in
|
|
135
|
+
self?.plugin.notifyBrowserPageNavigationCompletedListeners(BrowserPageNavigationCompletedEvent(url: url))
|
|
136
|
+
}
|
|
137
|
+
let navigationController = UINavigationController(rootViewController: webViewController)
|
|
138
|
+
navigationController.modalPresentationStyle = .fullScreen
|
|
139
|
+
navigationController.setNavigationBarHidden(!options.toolbar.visible, animated: false)
|
|
140
|
+
self.webViewController = webViewController
|
|
141
|
+
bridgeViewController.present(navigationController, animated: true) {
|
|
142
|
+
completion(nil)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@objc public func postMessage(_ options: PostMessageOptions, completion: @escaping (_ error: Error?) -> Void) {
|
|
149
|
+
DispatchQueue.main.async {
|
|
150
|
+
guard let webViewController = self.webViewController else {
|
|
151
|
+
completion(CustomError.noBrowserOpen)
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
let data = (try? JSONSerialization.data(withJSONObject: options.data)).flatMap { String(data: $0, encoding: .utf8) } ?? "{}"
|
|
155
|
+
webViewController.postMessage(data)
|
|
156
|
+
completion(nil)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public func safariViewController(_ controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) {
|
|
161
|
+
if didLoadSuccessfully {
|
|
162
|
+
plugin.notifyBrowserPageLoadedListeners()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
public func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
|
|
167
|
+
safariViewController = nil
|
|
168
|
+
plugin.notifyBrowserClosedListeners()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private func dismissActiveBrowser(_ completion: @escaping () -> Void) {
|
|
172
|
+
if let webViewController = self.webViewController {
|
|
173
|
+
webViewController.dismiss(animated: false) {
|
|
174
|
+
completion()
|
|
175
|
+
}
|
|
176
|
+
} else if let safariViewController = self.safariViewController {
|
|
177
|
+
safariViewController.dismiss(animated: false) {
|
|
178
|
+
self.safariViewController = nil
|
|
179
|
+
self.plugin.notifyBrowserClosedListeners()
|
|
180
|
+
completion()
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
completion()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private func handleMessageReceived(_ data: String) {
|
|
188
|
+
var value: Any = data
|
|
189
|
+
if let jsonData = data.data(using: .utf8), let parsedValue = try? JSONSerialization.jsonObject(with: jsonData, options: [.fragmentsAllowed]) {
|
|
190
|
+
value = parsedValue
|
|
191
|
+
}
|
|
192
|
+
plugin.notifyMessageReceivedListeners(MessageReceivedEvent(data: value))
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
public class InAppBrowserHelper {
|
|
5
|
+
public static func isCookie(_ cookie: HTTPCookie, matchingURL url: URL) -> Bool {
|
|
6
|
+
guard let host = url.host else {
|
|
7
|
+
return false
|
|
8
|
+
}
|
|
9
|
+
guard isHost(host, matchingDomain: cookie.domain) else {
|
|
10
|
+
return false
|
|
11
|
+
}
|
|
12
|
+
return isPath(url.path.isEmpty ? "/" : url.path, matchingCookiePath: cookie.path)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public static func parseColor(_ string: String) -> UIColor? {
|
|
16
|
+
guard string.hasPrefix("#") else {
|
|
17
|
+
return nil
|
|
18
|
+
}
|
|
19
|
+
let hex = String(string.dropFirst())
|
|
20
|
+
guard hex.count == 6 || hex.count == 8, let value = UInt64(hex, radix: 16) else {
|
|
21
|
+
return nil
|
|
22
|
+
}
|
|
23
|
+
let alpha = hex.count == 8 ? CGFloat((value >> 24) & 0xFF) / 255.0 : 1.0
|
|
24
|
+
let red = CGFloat((value >> 16) & 0xFF) / 255.0
|
|
25
|
+
let green = CGFloat((value >> 8) & 0xFF) / 255.0
|
|
26
|
+
let blue = CGFloat(value & 0xFF) / 255.0
|
|
27
|
+
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private static func isHost(_ host: String, matchingDomain domain: String) -> Bool {
|
|
31
|
+
let normalizedDomain = (domain.hasPrefix(".") ? String(domain.dropFirst()) : domain).lowercased()
|
|
32
|
+
let normalizedHost = host.lowercased()
|
|
33
|
+
return normalizedHost == normalizedDomain || normalizedHost.hasSuffix("." + normalizedDomain)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private static func isPath(_ path: String, matchingCookiePath cookiePath: String) -> Bool {
|
|
37
|
+
if path == cookiePath {
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
guard path.hasPrefix(cookiePath) else {
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
if cookiePath.hasSuffix("/") {
|
|
44
|
+
return true
|
|
45
|
+
}
|
|
46
|
+
let index = path.index(path.startIndex, offsetBy: cookiePath.count)
|
|
47
|
+
return path[index] == "/"
|
|
48
|
+
}
|
|
49
|
+
}
|