@calvingoh-hexa/capacitor-inappbrowser 6.9.36

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.
Files changed (55) hide show
  1. package/CapgoInappbrowser.podspec +17 -0
  2. package/LICENSE +21 -0
  3. package/README.md +690 -0
  4. package/android/build.gradle +64 -0
  5. package/android/src/main/AndroidManifest.xml +12 -0
  6. package/android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java +741 -0
  7. package/android/src/main/java/ee/forgr/capacitor_inappbrowser/Options.java +340 -0
  8. package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewCallbacks.java +15 -0
  9. package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +1177 -0
  10. package/android/src/main/res/.gitkeep +0 -0
  11. package/android/src/main/res/drawable/arrow_back_disabled.xml +9 -0
  12. package/android/src/main/res/drawable/arrow_back_enabled.xml +9 -0
  13. package/android/src/main/res/drawable/arrow_forward_disabled.xml +9 -0
  14. package/android/src/main/res/drawable/arrow_forward_enabled.xml +9 -0
  15. package/android/src/main/res/drawable/ic_clear_24px.xml +9 -0
  16. package/android/src/main/res/drawable/ic_refresh.xml +9 -0
  17. package/android/src/main/res/layout/activity_browser.xml +22 -0
  18. package/android/src/main/res/layout/bridge_layout_main.xml +15 -0
  19. package/android/src/main/res/layout/content_browser.xml +16 -0
  20. package/android/src/main/res/layout/tool_bar.xml +72 -0
  21. package/android/src/main/res/values/browser_theme.xml +3 -0
  22. package/android/src/main/res/values/colors.xml +5 -0
  23. package/android/src/main/res/values/dimens.xml +3 -0
  24. package/android/src/main/res/values/strings.xml +11 -0
  25. package/android/src/main/res/values/styles.xml +4 -0
  26. package/dist/docs.json +1865 -0
  27. package/dist/esm/definitions.d.ts +361 -0
  28. package/dist/esm/definitions.js +13 -0
  29. package/dist/esm/definitions.js.map +1 -0
  30. package/dist/esm/index.d.ts +4 -0
  31. package/dist/esm/index.js +7 -0
  32. package/dist/esm/index.js.map +1 -0
  33. package/dist/esm/web.d.ts +19 -0
  34. package/dist/esm/web.js +48 -0
  35. package/dist/esm/web.js.map +1 -0
  36. package/dist/plugin.cjs.js +75 -0
  37. package/dist/plugin.cjs.js.map +1 -0
  38. package/dist/plugin.js +78 -0
  39. package/dist/plugin.js.map +1 -0
  40. package/ios/Plugin/Assets.xcassets/Back.imageset/Back.png +0 -0
  41. package/ios/Plugin/Assets.xcassets/Back.imageset/Back@2x.png +0 -0
  42. package/ios/Plugin/Assets.xcassets/Back.imageset/Back@3x.png +0 -0
  43. package/ios/Plugin/Assets.xcassets/Back.imageset/Contents.json +26 -0
  44. package/ios/Plugin/Assets.xcassets/Contents.json +6 -0
  45. package/ios/Plugin/Assets.xcassets/Forward.imageset/Contents.json +26 -0
  46. package/ios/Plugin/Assets.xcassets/Forward.imageset/Forward.png +0 -0
  47. package/ios/Plugin/Assets.xcassets/Forward.imageset/Forward@2x.png +0 -0
  48. package/ios/Plugin/Assets.xcassets/Forward.imageset/Forward@3x.png +0 -0
  49. package/ios/Plugin/Enums.swift +65 -0
  50. package/ios/Plugin/InAppBrowserPlugin.h +10 -0
  51. package/ios/Plugin/InAppBrowserPlugin.m +21 -0
  52. package/ios/Plugin/InAppBrowserPlugin.swift +434 -0
  53. package/ios/Plugin/Info.plist +24 -0
  54. package/ios/Plugin/WKWebViewController.swift +1021 -0
  55. package/package.json +83 -0
@@ -0,0 +1,1021 @@
1
+ //
2
+ // WKWebViewController.swift
3
+ // Sample
4
+ //
5
+ // Created by Meniny on 2018-01-20.
6
+ // Copyright © 2018年 Meniny. All rights reserved.
7
+ //
8
+
9
+ import UIKit
10
+ import WebKit
11
+
12
+ private let estimatedProgressKeyPath = "estimatedProgress"
13
+ private let titleKeyPath = "title"
14
+ private let cookieKey = "Cookie"
15
+
16
+ private struct UrlsHandledByApp {
17
+ static var hosts = ["itunes.apple.com"]
18
+ static var schemes = ["tel", "mailto", "sms"]
19
+ static var blank = true
20
+ }
21
+
22
+ public struct WKWebViewCredentials {
23
+ var username: String
24
+ var password: String
25
+ }
26
+
27
+ @objc public protocol WKWebViewControllerDelegate {
28
+ @objc optional func webViewController(_ controller: WKWebViewController, canDismiss url: URL) -> Bool
29
+
30
+ @objc optional func webViewController(_ controller: WKWebViewController, didStart url: URL)
31
+ @objc optional func webViewController(_ controller: WKWebViewController, didFinish url: URL)
32
+ @objc optional func webViewController(_ controller: WKWebViewController, didFail url: URL, withError error: Error)
33
+ @objc optional func webViewController(_ controller: WKWebViewController, decidePolicy url: URL, navigationType: NavigationType) -> Bool
34
+ }
35
+
36
+ extension Dictionary {
37
+ func mapKeys<T>(_ transform: (Key) throws -> T) rethrows -> [T: Value] {
38
+ var dictionary = [T: Value]()
39
+ for (key, value) in self {
40
+ dictionary[try transform(key)] = value
41
+ }
42
+ return dictionary
43
+ }
44
+ }
45
+
46
+ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
47
+
48
+ public init() {
49
+ super.init(nibName: nil, bundle: nil)
50
+ }
51
+
52
+ public required init?(coder aDecoder: NSCoder) {
53
+ super.init(coder: aDecoder)
54
+ }
55
+
56
+ public init(source: WKWebSource?, credentials: WKWebViewCredentials? = nil) {
57
+ super.init(nibName: nil, bundle: nil)
58
+ self.source = source
59
+ self.credentials = credentials
60
+ self.initWebview()
61
+ }
62
+
63
+ public init(url: URL, credentials: WKWebViewCredentials? = nil) {
64
+ super.init(nibName: nil, bundle: nil)
65
+ self.source = .remote(url)
66
+ self.credentials = credentials
67
+ self.initWebview()
68
+ }
69
+
70
+ public init(url: URL, headers: [String: String], isInspectable: Bool, credentials: WKWebViewCredentials? = nil, preventDeeplink: Bool) {
71
+ super.init(nibName: nil, bundle: nil)
72
+ self.source = .remote(url)
73
+ self.credentials = credentials
74
+ self.setHeaders(headers: headers)
75
+ self.setPreventDeeplink(preventDeeplink: preventDeeplink)
76
+ self.initWebview(isInspectable: isInspectable)
77
+ }
78
+
79
+ open var hasDynamicTitle = false
80
+ open var source: WKWebSource?
81
+ /// use `source` instead
82
+ open internal(set) var url: URL?
83
+ open var tintColor: UIColor?
84
+ open var allowsFileURL = true
85
+ open var delegate: WKWebViewControllerDelegate?
86
+ open var bypassedSSLHosts: [String]?
87
+ open var cookies: [HTTPCookie]?
88
+ open var headers: [String: String]?
89
+ open var capBrowserPlugin: InAppBrowserPlugin?
90
+ var shareDisclaimer: [String: Any]?
91
+ var shareSubject: String?
92
+ var didpageInit = false
93
+ var viewHeightLandscape: CGFloat?
94
+ var viewHeightPortrait: CGFloat?
95
+ var currentViewHeight: CGFloat?
96
+ open var closeModal = false
97
+ open var closeModalTitle = ""
98
+ open var closeModalDescription = ""
99
+ open var closeModalOk = ""
100
+ open var closeModalCancel = ""
101
+ open var ignoreUntrustedSSLError = false
102
+ var viewWasPresented = false
103
+ var preventDeeplink: Bool = false
104
+
105
+ internal var preShowSemaphore: DispatchSemaphore?
106
+ internal var preShowError: String?
107
+
108
+ func setHeaders(headers: [String: String]) {
109
+ self.headers = headers
110
+ let lowercasedHeaders = headers.mapKeys { $0.lowercased() }
111
+ let userAgent = lowercasedHeaders["user-agent"]
112
+ self.headers?.removeValue(forKey: "User-Agent")
113
+ self.headers?.removeValue(forKey: "user-agent")
114
+
115
+ if let userAgent = userAgent {
116
+ self.customUserAgent = userAgent
117
+ }
118
+ }
119
+
120
+ func setPreventDeeplink(preventDeeplink: Bool) {
121
+ self.preventDeeplink = preventDeeplink
122
+ }
123
+
124
+ internal var customUserAgent: String? {
125
+ didSet {
126
+ guard let agent = userAgent else {
127
+ return
128
+ }
129
+ webView?.customUserAgent = agent
130
+ }
131
+ }
132
+
133
+ open var userAgent: String? {
134
+ didSet {
135
+ guard let originalUserAgent = originalUserAgent, let userAgent = userAgent else {
136
+ return
137
+ }
138
+ webView?.customUserAgent = [originalUserAgent, userAgent].joined(separator: " ")
139
+ }
140
+ }
141
+
142
+ open var pureUserAgent: String? {
143
+ didSet {
144
+ guard let agent = pureUserAgent else {
145
+ return
146
+ }
147
+ webView?.customUserAgent = agent
148
+ }
149
+ }
150
+
151
+ open var websiteTitleInNavigationBar = true
152
+ open var doneBarButtonItemPosition: NavigationBarPosition = .right
153
+ open var preShowScript: String?
154
+ open var leftNavigationBarItemTypes: [BarButtonItemType] = []
155
+ open var rightNavigaionBarItemTypes: [BarButtonItemType] = []
156
+ open var toolbarItemTypes: [BarButtonItemType] = [.back, .forward, .reload, .activity]
157
+
158
+ open var backBarButtonItemImage: UIImage?
159
+ open var forwardBarButtonItemImage: UIImage?
160
+ open var reloadBarButtonItemImage: UIImage?
161
+ open var stopBarButtonItemImage: UIImage?
162
+ open var activityBarButtonItemImage: UIImage?
163
+
164
+ open var buttonNearDoneIcon: UIImage?
165
+
166
+ fileprivate var webView: WKWebView?
167
+ fileprivate var progressView: UIProgressView?
168
+
169
+ fileprivate var previousNavigationBarState: (tintColor: UIColor, hidden: Bool) = (.black, false)
170
+ fileprivate var previousToolbarState: (tintColor: UIColor, hidden: Bool) = (.black, false)
171
+
172
+ fileprivate var originalUserAgent: String?
173
+
174
+ fileprivate lazy var backBarButtonItem: UIBarButtonItem = {
175
+ let bundle = Bundle(for: WKWebViewController.self)
176
+ return UIBarButtonItem(image: backBarButtonItemImage ?? UIImage(named: "Back", in: bundle, compatibleWith: nil), style: .plain, target: self, action: #selector(backDidClick(sender:)))
177
+ }()
178
+
179
+ fileprivate lazy var forwardBarButtonItem: UIBarButtonItem = {
180
+ let bundle = Bundle(for: WKWebViewController.self)
181
+ return UIBarButtonItem(image: forwardBarButtonItemImage ?? UIImage(named: "Forward", in: bundle, compatibleWith: nil), style: .plain, target: self, action: #selector(forwardDidClick(sender:)))
182
+ }()
183
+
184
+ fileprivate lazy var reloadBarButtonItem: UIBarButtonItem = {
185
+ if let image = reloadBarButtonItemImage {
186
+ return UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(reloadDidClick(sender:)))
187
+ } else {
188
+ return UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(reloadDidClick(sender:)))
189
+ }
190
+ }()
191
+
192
+ fileprivate lazy var stopBarButtonItem: UIBarButtonItem = {
193
+ if let image = stopBarButtonItemImage {
194
+ return UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(stopDidClick(sender:)))
195
+ } else {
196
+ return UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(stopDidClick(sender:)))
197
+ }
198
+ }()
199
+
200
+ fileprivate lazy var activityBarButtonItem: UIBarButtonItem = {
201
+ if let image = activityBarButtonItemImage {
202
+ return UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(activityDidClick(sender:)))
203
+ } else {
204
+ return UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(activityDidClick(sender:)))
205
+ }
206
+ }()
207
+
208
+ fileprivate lazy var doneBarButtonItem: UIBarButtonItem = {
209
+ return UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneDidClick(sender:)))
210
+ }()
211
+
212
+ fileprivate lazy var flexibleSpaceBarButtonItem: UIBarButtonItem = {
213
+ return UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
214
+ }()
215
+
216
+ fileprivate var credentials: WKWebViewCredentials?
217
+
218
+ deinit {
219
+ webView?.removeObserver(self, forKeyPath: estimatedProgressKeyPath)
220
+ if websiteTitleInNavigationBar {
221
+ webView?.removeObserver(self, forKeyPath: titleKeyPath)
222
+ }
223
+ webView?.removeObserver(self, forKeyPath: #keyPath(WKWebView.url))
224
+ }
225
+
226
+ override open func viewDidLoad() {
227
+ super.viewDidLoad()
228
+ if self.webView == nil {
229
+ self.initWebview()
230
+ }
231
+ }
232
+
233
+ open func setCredentials(credentials: WKWebViewCredentials?) {
234
+ self.credentials = credentials
235
+ }
236
+
237
+ // Method to send a message from Swift to JavaScript
238
+ open func postMessageToJS(message: [String: Any]) {
239
+ if let jsonData = try? JSONSerialization.data(withJSONObject: message, options: []),
240
+ let jsonString = String(data: jsonData, encoding: .utf8) {
241
+ let script = "window.dispatchEvent(new CustomEvent('messageFromNative', { detail: \(jsonString) }));"
242
+ webView?.evaluateJavaScript(script, completionHandler: nil)
243
+ }
244
+ }
245
+
246
+ // Method to receive messages from JavaScript
247
+ public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
248
+ if message.name == "messageHandler" {
249
+ if let messageBody = message.body as? [String: Any] {
250
+ print("Received message from JavaScript:", messageBody)
251
+ self.capBrowserPlugin?.notifyListeners("messageFromWebview", data: messageBody)
252
+ } else {
253
+ print("Received non-dictionary message from JavaScript:", message.body)
254
+ self.capBrowserPlugin?.notifyListeners("messageFromWebview", data: ["rawMessage": String(describing: message.body)])
255
+ }
256
+ } else if message.name == "preShowScriptSuccess" {
257
+ guard let semaphore = preShowSemaphore else {
258
+ print("[InAppBrowser - preShowScriptSuccess]: Semaphore not found")
259
+ return
260
+ }
261
+
262
+ semaphore.signal()
263
+ } else if message.name == "preShowScriptError" {
264
+ guard let semaphore = preShowSemaphore else {
265
+ print("[InAppBrowser - preShowScriptError]: Semaphore not found")
266
+ return
267
+ }
268
+ print("[InAppBrowser - preShowScriptError]: Error!!!!")
269
+ semaphore.signal()
270
+ }
271
+ }
272
+
273
+ func injectJavaScriptInterface() {
274
+ let script = """
275
+ if (!window.mobileApp) {
276
+ window.mobileApp = {
277
+ postMessage: function(message) {
278
+ if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.messageHandler) {
279
+ window.webkit.messageHandlers.messageHandler.postMessage(message);
280
+ }
281
+ }
282
+ };
283
+ }
284
+ """
285
+ DispatchQueue.main.async {
286
+ self.webView?.evaluateJavaScript(script, completionHandler: nil)
287
+ }
288
+ }
289
+
290
+ open func initWebview(isInspectable: Bool = true) {
291
+
292
+ self.view.backgroundColor = UIColor.white
293
+
294
+ self.extendedLayoutIncludesOpaqueBars = true
295
+ self.edgesForExtendedLayout = [.bottom]
296
+
297
+ let webConfiguration = WKWebViewConfiguration()
298
+ let userContentController = WKUserContentController()
299
+ userContentController.add(self, name: "messageHandler")
300
+ userContentController.add(self, name: "preShowScriptError")
301
+ userContentController.add(self, name: "preShowScriptSuccess")
302
+ webConfiguration.userContentController = userContentController
303
+ let webView = WKWebView(frame: .zero, configuration: webConfiguration)
304
+
305
+ if webView.responds(to: Selector(("setInspectable:"))) {
306
+ // Fix: https://stackoverflow.com/questions/76216183/how-to-debug-wkwebview-in-ios-16-4-1-using-xcode-14-2/76603043#76603043
307
+ webView.perform(Selector(("setInspectable:")), with: isInspectable)
308
+ }
309
+
310
+ webView.uiDelegate = self
311
+ webView.navigationDelegate = self
312
+
313
+ webView.allowsBackForwardNavigationGestures = true
314
+ webView.isMultipleTouchEnabled = true
315
+
316
+ webView.addObserver(self, forKeyPath: estimatedProgressKeyPath, options: .new, context: nil)
317
+ if websiteTitleInNavigationBar {
318
+ webView.addObserver(self, forKeyPath: titleKeyPath, options: .new, context: nil)
319
+ }
320
+ webView.addObserver(self, forKeyPath: #keyPath(WKWebView.url), options: .new, context: nil)
321
+ // NotificationCenter.default.addObserver(self, selector: #selector(restateViewHeight), name: UIDevice.orientationDidChangeNotification, object: nil)
322
+
323
+ self.view = webView
324
+ self.webView = webView
325
+
326
+ self.webView?.customUserAgent = self.customUserAgent ?? self.userAgent ?? self.originalUserAgent
327
+
328
+ self.navigationItem.title = self.navigationItem.title ?? self.source?.absoluteString
329
+
330
+ if let navigation = self.navigationController {
331
+ self.previousNavigationBarState = (navigation.navigationBar.tintColor, navigation.navigationBar.isHidden)
332
+ self.previousToolbarState = (navigation.toolbar.tintColor, navigation.toolbar.isHidden)
333
+ }
334
+
335
+ if let s = self.source {
336
+ self.load(source: s)
337
+ } else {
338
+ print("[\(type(of: self))][Error] Invalid url")
339
+ }
340
+ }
341
+
342
+ open func setupViewElements() {
343
+ self.setUpProgressView()
344
+ self.setUpConstraints()
345
+ self.addBarButtonItems()
346
+ }
347
+
348
+ @objc func restateViewHeight() {
349
+ var bottomPadding = CGFloat(0.0)
350
+ var topPadding = CGFloat(0.0)
351
+ if #available(iOS 11.0, *) {
352
+ let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow })
353
+ bottomPadding = window?.safeAreaInsets.bottom ?? 0.0
354
+ topPadding = window?.safeAreaInsets.top ?? 0.0
355
+ }
356
+ if UIDevice.current.orientation.isPortrait {
357
+ self.navigationController?.toolbar.isHidden = false
358
+ if self.viewHeightPortrait == nil {
359
+ self.viewHeightPortrait = self.view.safeAreaLayoutGuide.layoutFrame.size.height
360
+ if toolbarItemTypes.count == 0 {
361
+ self.viewHeightPortrait! += bottomPadding
362
+ }
363
+ if self.navigationController?.navigationBar.isHidden == true {
364
+ self.viewHeightPortrait! += topPadding
365
+ }
366
+ }
367
+ self.currentViewHeight = self.viewHeightPortrait
368
+ } else if UIDevice.current.orientation.isLandscape {
369
+ self.navigationController?.toolbar.isHidden = false
370
+ if self.viewHeightLandscape == nil {
371
+ self.viewHeightLandscape = self.view.safeAreaLayoutGuide.layoutFrame.size.height
372
+ if toolbarItemTypes.count == 0 {
373
+ self.viewHeightLandscape! += bottomPadding
374
+ }
375
+ if self.navigationController?.navigationBar.isHidden == true {
376
+ self.viewHeightLandscape! += topPadding
377
+ }
378
+ }
379
+ self.currentViewHeight = self.viewHeightLandscape
380
+ }
381
+ }
382
+
383
+ override open func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
384
+ // self.view.frame.size.height = self.currentViewHeight!
385
+ }
386
+
387
+ override open func viewWillLayoutSubviews() {
388
+ restateViewHeight()
389
+ if self.currentViewHeight != nil {
390
+ self.view.frame.size.height = self.currentViewHeight!
391
+ }
392
+ }
393
+
394
+ override open func viewWillAppear(_ animated: Bool) {
395
+ super.viewWillAppear(animated)
396
+ if !self.viewWasPresented {
397
+ self.setupViewElements()
398
+ setUpState()
399
+ self.viewWasPresented = true
400
+ }
401
+ }
402
+
403
+ override open func viewWillDisappear(_ animated: Bool) {
404
+ super.viewWillDisappear(animated)
405
+ rollbackState()
406
+ }
407
+
408
+ override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
409
+ switch keyPath {
410
+ case estimatedProgressKeyPath?:
411
+ DispatchQueue.main.async {
412
+ guard let estimatedProgress = self.webView?.estimatedProgress else {
413
+ return
414
+ }
415
+ self.progressView?.alpha = 1
416
+ self.progressView?.setProgress(Float(estimatedProgress), animated: true)
417
+
418
+ if estimatedProgress >= 1.0 {
419
+ UIView.animate(withDuration: 0.3, delay: 0.3, options: .curveEaseOut, animations: {
420
+ self.progressView?.alpha = 0
421
+ }, completion: {
422
+ _ in
423
+ self.progressView?.setProgress(0, animated: false)
424
+ })
425
+ }
426
+ }
427
+ case titleKeyPath?:
428
+ if self.hasDynamicTitle {
429
+ self.navigationItem.title = webView?.url?.host
430
+ }
431
+ case "URL":
432
+
433
+ self.capBrowserPlugin?.notifyListeners("urlChangeEvent", data: ["url": webView?.url?.absoluteString ?? ""])
434
+ self.injectJavaScriptInterface()
435
+ default:
436
+ super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
437
+ }
438
+ }
439
+ }
440
+
441
+ // MARK: - Public Methods
442
+ public extension WKWebViewController {
443
+
444
+ func load(source s: WKWebSource) {
445
+ switch s {
446
+ case .remote(let url):
447
+ self.load(remote: url)
448
+ case .file(let url, access: let access):
449
+ self.load(file: url, access: access)
450
+ case .string(let str, base: let base):
451
+ self.load(string: str, base: base)
452
+ }
453
+ }
454
+
455
+ func load(remote: URL) {
456
+ DispatchQueue.main.async {
457
+ self.webView?.load(self.createRequest(url: remote))
458
+ }
459
+ }
460
+
461
+ func load(file: URL, access: URL) {
462
+ webView?.loadFileURL(file, allowingReadAccessTo: access)
463
+ }
464
+
465
+ func load(string: String, base: URL? = nil) {
466
+ webView?.loadHTMLString(string, baseURL: base)
467
+ }
468
+
469
+ func goBackToFirstPage() {
470
+ if let firstPageItem = webView?.backForwardList.backList.first {
471
+ webView?.go(to: firstPageItem)
472
+ }
473
+ }
474
+ func reload() {
475
+ webView?.reload()
476
+ }
477
+
478
+ func executeScript(script: String, completion: ((Any?, Error?) -> Void)? = nil) {
479
+ DispatchQueue.main.async { [weak self] in
480
+ self?.webView?.evaluateJavaScript(script, completionHandler: completion)
481
+ }
482
+ }
483
+ }
484
+
485
+ // MARK: - Fileprivate Methods
486
+ fileprivate extension WKWebViewController {
487
+ var availableCookies: [HTTPCookie]? {
488
+ return cookies?.filter {
489
+ cookie in
490
+ var result = true
491
+ let url = self.source?.remoteURL
492
+ if let host = url?.host, !cookie.domain.hasSuffix(host) {
493
+ result = false
494
+ }
495
+ if cookie.isSecure && url?.scheme != "https" {
496
+ result = false
497
+ }
498
+
499
+ return result
500
+ }
501
+ }
502
+ func createRequest(url: URL) -> URLRequest {
503
+ var request = URLRequest(url: url)
504
+
505
+ // Set up headers
506
+ if let headers = headers {
507
+ for (field, value) in headers {
508
+ request.addValue(value, forHTTPHeaderField: field)
509
+ }
510
+ }
511
+
512
+ // Set up Cookies
513
+ if let cookies = availableCookies, let value = HTTPCookie.requestHeaderFields(with: cookies)[cookieKey] {
514
+ request.addValue(value, forHTTPHeaderField: cookieKey)
515
+ }
516
+
517
+ return request
518
+ }
519
+
520
+ func setUpProgressView() {
521
+ let progressView = UIProgressView(progressViewStyle: .default)
522
+ progressView.trackTintColor = UIColor(white: 1, alpha: 0)
523
+ self.progressView = progressView
524
+ // updateProgressViewFrame()
525
+ }
526
+
527
+ func setUpConstraints() {
528
+ if !(self.navigationController?.navigationBar.isHidden)! {
529
+ self.progressView?.frame.origin.y = CGFloat((self.navigationController?.navigationBar.frame.height)!)
530
+ self.navigationController?.navigationBar.addSubview(self.progressView!)
531
+ webView?.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)
532
+ }
533
+ }
534
+
535
+ func addBarButtonItems() {
536
+ func barButtonItem(_ type: BarButtonItemType) -> UIBarButtonItem? {
537
+ switch type {
538
+ case .back:
539
+ return backBarButtonItem
540
+ case .forward:
541
+ return forwardBarButtonItem
542
+ case .reload:
543
+ return reloadBarButtonItem
544
+ case .stop:
545
+ return stopBarButtonItem
546
+ case .activity:
547
+ return activityBarButtonItem
548
+ case .done:
549
+ return doneBarButtonItem
550
+ case .flexibleSpace:
551
+ return flexibleSpaceBarButtonItem
552
+ case .custom(let icon, let title, let action):
553
+ let item: BlockBarButtonItem
554
+ if let icon = icon {
555
+ item = BlockBarButtonItem(image: icon, style: .plain, target: self, action: #selector(customDidClick(sender:)))
556
+ } else {
557
+ item = BlockBarButtonItem(title: title, style: .plain, target: self, action: #selector(customDidClick(sender:)))
558
+ }
559
+ item.block = action
560
+ return item
561
+ }
562
+ }
563
+
564
+ // if presentingViewController != nil {
565
+ switch doneBarButtonItemPosition {
566
+ case .left:
567
+ if !leftNavigationBarItemTypes.contains(where: { type in
568
+ switch type {
569
+ case .done:
570
+ return true
571
+ default:
572
+ return false
573
+ }
574
+ }) {
575
+ leftNavigationBarItemTypes.insert(.done, at: 0)
576
+ }
577
+ case .right:
578
+ if !rightNavigaionBarItemTypes.contains(where: { type in
579
+ switch type {
580
+ case .done:
581
+ return true
582
+ default:
583
+ return false
584
+ }
585
+ }) {
586
+ rightNavigaionBarItemTypes.insert(.done, at: 0)
587
+ }
588
+ case .none:
589
+ break
590
+ }
591
+ // }
592
+
593
+ navigationItem.leftBarButtonItems = leftNavigationBarItemTypes.map {
594
+ barButtonItemType in
595
+ if let barButtonItem = barButtonItem(barButtonItemType) {
596
+ return barButtonItem
597
+ }
598
+ return UIBarButtonItem()
599
+ }
600
+
601
+ var rightBarButtons = rightNavigaionBarItemTypes.map {
602
+ barButtonItemType in
603
+ if let barButtonItem = barButtonItem(barButtonItemType) {
604
+ return barButtonItem
605
+ }
606
+ return UIBarButtonItem()
607
+ }
608
+ if rightBarButtons.count == 1 && buttonNearDoneIcon != nil && rightBarButtons[0] == doneBarButtonItem {
609
+ rightBarButtons.append(UIBarButtonItem(image: buttonNearDoneIcon, style: .plain, target: self, action: #selector(buttonNearDoneDidClick)))
610
+ }
611
+ navigationItem.rightBarButtonItems = rightBarButtons
612
+
613
+ if toolbarItemTypes.count > 0 {
614
+ for index in 0..<toolbarItemTypes.count - 1 {
615
+ toolbarItemTypes.insert(.flexibleSpace, at: 2 * index + 1)
616
+ }
617
+ }
618
+
619
+ let gen = toolbarItemTypes.map {
620
+ barButtonItemType -> UIBarButtonItem in
621
+ if let barButtonItem = barButtonItem(barButtonItemType) {
622
+ return barButtonItem
623
+ }
624
+ return UIBarButtonItem()
625
+ }
626
+ setToolbarItems(gen, animated: true)
627
+ }
628
+
629
+ func updateBarButtonItems() {
630
+ backBarButtonItem.isEnabled = webView?.canGoBack ?? false
631
+ forwardBarButtonItem.isEnabled = webView?.canGoForward ?? false
632
+
633
+ let updateReloadBarButtonItem: (UIBarButtonItem, Bool) -> UIBarButtonItem = {
634
+ [unowned self] barButtonItem, isLoading in
635
+ switch barButtonItem {
636
+ case self.reloadBarButtonItem:
637
+ fallthrough
638
+ case self.stopBarButtonItem:
639
+ return isLoading ? self.stopBarButtonItem : self.reloadBarButtonItem
640
+ default:
641
+ break
642
+ }
643
+ return barButtonItem
644
+ }
645
+
646
+ let isLoading = webView?.isLoading ?? false
647
+ toolbarItems = toolbarItems?.map {
648
+ barButtonItem -> UIBarButtonItem in
649
+ return updateReloadBarButtonItem(barButtonItem, isLoading)
650
+ }
651
+
652
+ navigationItem.leftBarButtonItems = navigationItem.leftBarButtonItems?.map {
653
+ barButtonItem -> UIBarButtonItem in
654
+ return updateReloadBarButtonItem(barButtonItem, isLoading)
655
+ }
656
+
657
+ navigationItem.rightBarButtonItems = navigationItem.rightBarButtonItems?.map {
658
+ barButtonItem -> UIBarButtonItem in
659
+ return updateReloadBarButtonItem(barButtonItem, isLoading)
660
+ }
661
+ }
662
+
663
+ func setUpState() {
664
+ navigationController?.setNavigationBarHidden(false, animated: true)
665
+ navigationController?.setToolbarHidden(toolbarItemTypes.count == 0, animated: true)
666
+
667
+ if let tintColor = tintColor {
668
+ progressView?.progressTintColor = tintColor
669
+ navigationController?.navigationBar.tintColor = tintColor
670
+ navigationController?.toolbar.tintColor = tintColor
671
+ }
672
+ }
673
+
674
+ func rollbackState() {
675
+ progressView?.progress = 0
676
+
677
+ navigationController?.navigationBar.tintColor = previousNavigationBarState.tintColor
678
+ navigationController?.toolbar.tintColor = previousToolbarState.tintColor
679
+
680
+ navigationController?.setToolbarHidden(previousToolbarState.hidden, animated: true)
681
+ navigationController?.setNavigationBarHidden(previousNavigationBarState.hidden, animated: true)
682
+ }
683
+
684
+ func checkRequestCookies(_ request: URLRequest, cookies: [HTTPCookie]) -> Bool {
685
+ if cookies.count <= 0 {
686
+ return true
687
+ }
688
+ guard let headerFields = request.allHTTPHeaderFields, let cookieString = headerFields[cookieKey] else {
689
+ return false
690
+ }
691
+
692
+ let requestCookies = cookieString.components(separatedBy: ";").map {
693
+ $0.trimmingCharacters(in: .whitespacesAndNewlines).split(separator: "=", maxSplits: 1).map(String.init)
694
+ }
695
+
696
+ var valid = false
697
+ for cookie in cookies {
698
+ valid = requestCookies.filter {
699
+ $0[0] == cookie.name && $0[1] == cookie.value
700
+ }.count > 0
701
+ if !valid {
702
+ break
703
+ }
704
+ }
705
+ return valid
706
+ }
707
+
708
+ func openURLWithApp(_ url: URL) -> Bool {
709
+ let application = UIApplication.shared
710
+ if application.canOpenURL(url) {
711
+ application.open(url, options: [:], completionHandler: nil)
712
+ return true
713
+ }
714
+
715
+ return false
716
+ }
717
+
718
+ func handleURLWithApp(_ url: URL, targetFrame: WKFrameInfo?) -> Bool {
719
+ let hosts = UrlsHandledByApp.hosts
720
+ let schemes = UrlsHandledByApp.schemes
721
+ let blank = UrlsHandledByApp.blank
722
+
723
+ var tryToOpenURLWithApp = false
724
+ if let host = url.host, hosts.contains(host) {
725
+ tryToOpenURLWithApp = true
726
+ }
727
+ if let scheme = url.scheme, schemes.contains(scheme) {
728
+ tryToOpenURLWithApp = true
729
+ }
730
+ if blank && targetFrame == nil {
731
+ tryToOpenURLWithApp = true
732
+ }
733
+
734
+ return tryToOpenURLWithApp ? openURLWithApp(url) : false
735
+ }
736
+
737
+ @objc func backDidClick(sender: AnyObject) {
738
+ webView?.goBack()
739
+ }
740
+
741
+ @objc func forwardDidClick(sender: AnyObject) {
742
+ webView?.goForward()
743
+ }
744
+
745
+ @objc func buttonNearDoneDidClick(sender: AnyObject) {
746
+ self.capBrowserPlugin?.notifyListeners("buttonNearDoneClick", data: [:])
747
+ }
748
+
749
+ @objc func reloadDidClick(sender: AnyObject) {
750
+ webView?.stopLoading()
751
+ if webView?.url != nil {
752
+ webView?.reload()
753
+ } else if let s = self.source {
754
+ self.load(source: s)
755
+ }
756
+ }
757
+
758
+ @objc func stopDidClick(sender: AnyObject) {
759
+ webView?.stopLoading()
760
+ }
761
+
762
+ @objc func activityDidClick(sender: AnyObject) {
763
+ guard let s = self.source else {
764
+ return
765
+ }
766
+
767
+ let items: [Any]
768
+ switch s {
769
+ case .remote(let u):
770
+ items = [u]
771
+ case .file(let u, access: _):
772
+ items = [u]
773
+ case .string(let str, base: _):
774
+ items = [str]
775
+ }
776
+ showDisclaimer(items: items, sender: sender)
777
+ }
778
+
779
+ func showDisclaimer(items: [Any], sender: AnyObject) {
780
+ let showDisclaimer: Bool = self.shareDisclaimer != nil
781
+ if showDisclaimer {
782
+ let alert = UIAlertController(
783
+ title: self.shareDisclaimer?["title"] as? String ?? "Title",
784
+ message: self.shareDisclaimer?["message"] as? String ?? "Message",
785
+ preferredStyle: UIAlertController.Style.alert)
786
+ alert.addAction(UIAlertAction(title: self.shareDisclaimer?["confirmBtn"] as? String ?? "Confirm", style: UIAlertAction.Style.default, handler: { _ in
787
+ self.shareDisclaimer = nil
788
+ self.capBrowserPlugin?.notifyListeners("confirmBtnClicked", data: nil)
789
+ let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil)
790
+ activityViewController.setValue(self.shareSubject ?? self.title, forKey: "subject")
791
+ activityViewController.popoverPresentationController?.barButtonItem = (sender as! UIBarButtonItem)
792
+ self.present(activityViewController, animated: true, completion: nil)
793
+ }))
794
+ alert.addAction(UIAlertAction(title: self.shareDisclaimer?["cancelBtn"] as? String ?? "Cancel", style: UIAlertAction.Style.default, handler: nil))
795
+ self.present(alert, animated: true, completion: nil)
796
+ } else {
797
+ let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil)
798
+ #imageLiteral(resourceName: "simulator_screenshot_B8B44B8D-EB30-425C-9BF4-1F37697A8459.png")
799
+ activityViewController.setValue(self.shareSubject ?? self.title, forKey: "subject")
800
+ activityViewController.popoverPresentationController?.barButtonItem = (sender as! UIBarButtonItem)
801
+ self.present(activityViewController, animated: true, completion: nil)
802
+ }
803
+ }
804
+
805
+ func closeView () {
806
+ var canDismiss = true
807
+ if let url = self.source?.url {
808
+ canDismiss = delegate?.webViewController?(self, canDismiss: url) ?? true
809
+ }
810
+ if canDismiss {
811
+ // UIDevice.current.setValue(Int(UIInterfaceOrientation.portrait.rawValue), forKey: "orientation")
812
+ self.capBrowserPlugin?.notifyListeners("closeEvent", data: ["url": webView?.url?.absoluteString ?? ""])
813
+ dismiss(animated: true, completion: nil)
814
+ }
815
+ }
816
+
817
+ @objc func doneDidClick(sender: AnyObject) {
818
+ // check if closeModal is true, if true display alert before close
819
+ if self.closeModal {
820
+ let alert = UIAlertController(title: self.closeModalTitle, message: self.closeModalDescription, preferredStyle: UIAlertController.Style.alert)
821
+ alert.addAction(UIAlertAction(title: self.closeModalOk, style: UIAlertAction.Style.default, handler: { _ in
822
+ self.closeView()
823
+ }))
824
+ alert.addAction(UIAlertAction(title: self.closeModalCancel, style: UIAlertAction.Style.default, handler: nil))
825
+ self.present(alert, animated: true, completion: nil)
826
+ } else {
827
+ self.closeView()
828
+ }
829
+
830
+ }
831
+
832
+ @objc func customDidClick(sender: BlockBarButtonItem) {
833
+ sender.block?(self)
834
+ }
835
+
836
+ func canRotate() {}
837
+ }
838
+
839
+ // MARK: - WKUIDelegate
840
+ extension WKWebViewController: WKUIDelegate {
841
+
842
+ }
843
+
844
+ // MARK: - WKNavigationDelegate
845
+ extension WKWebViewController: WKNavigationDelegate {
846
+ internal func injectPreShowScript() {
847
+ if preShowSemaphore != nil {
848
+ return
849
+ }
850
+
851
+ // TODO: implement interface
852
+ let script = """
853
+ async function preShowFunction() {
854
+ \(self.preShowScript ?? "")
855
+ };
856
+ preShowFunction().then(
857
+ () => window.webkit.messageHandlers.preShowScriptSuccess.postMessage({})
858
+ ).catch(
859
+ err => {
860
+ console.error('Preshow error', err);
861
+ window.webkit.messageHandlers.preShowScriptError.postMessage(JSON.stringify(err, Object.getOwnPropertyNames(err)));
862
+ }
863
+ )
864
+ """
865
+ print("[InAppBrowser - InjectPreShowScript] PreShowScript script: \(script)")
866
+
867
+ self.preShowSemaphore = DispatchSemaphore(value: 0)
868
+ self.executeScript(script: script) // this will run on the main thread
869
+
870
+ defer {
871
+ self.preShowSemaphore = nil
872
+ self.preShowError = nil
873
+ }
874
+
875
+ if self.preShowSemaphore?.wait(timeout: .now() + 10) == .timedOut {
876
+ print("[InAppBrowser - InjectPreShowScript] PreShowScript running for over 10 seconds. The plugin will not wait any longer!")
877
+ return
878
+ }
879
+
880
+ // "async function preShowFunction() {\n" +
881
+ // self.preShowScript + "\n" +
882
+ // "};\n" +
883
+ // "preShowFunction().then(() => window.PreShowScriptInterface.success()).catch(err => { console.error('Preshow error', err); window.PreShowScriptInterface.error(JSON.stringify(err, Object.getOwnPropertyNames(err))) })";
884
+
885
+ }
886
+
887
+ public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
888
+ updateBarButtonItems()
889
+ self.progressView?.progress = 0
890
+ if let u = webView.url {
891
+ self.url = u
892
+ delegate?.webViewController?(self, didStart: u)
893
+ }
894
+ }
895
+ public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
896
+ if !didpageInit && self.capBrowserPlugin?.isPresentAfterPageLoad == true {
897
+ // injectPreShowScript will block, don't execute on the main thread
898
+ if self.preShowScript != nil && !self.preShowScript!.isEmpty {
899
+ DispatchQueue.global(qos: .userInitiated).async {
900
+ self.injectPreShowScript()
901
+ DispatchQueue.main.async { [weak self] in
902
+ self?.capBrowserPlugin?.presentView()
903
+ }
904
+ }
905
+ } else {
906
+ self.capBrowserPlugin?.presentView()
907
+ }
908
+ } else if self.preShowScript != nil && !self.preShowScript!.isEmpty && self.capBrowserPlugin?.isPresentAfterPageLoad == true {
909
+ DispatchQueue.global(qos: .userInitiated).async {
910
+ self.injectPreShowScript()
911
+ }
912
+ }
913
+ didpageInit = true
914
+ updateBarButtonItems()
915
+ self.progressView?.progress = 0
916
+ if let url = webView.url {
917
+ self.url = url
918
+ delegate?.webViewController?(self, didFinish: url)
919
+ }
920
+ self.injectJavaScriptInterface()
921
+ self.capBrowserPlugin?.notifyListeners("browserPageLoaded", data: [:])
922
+ }
923
+
924
+ public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
925
+ updateBarButtonItems()
926
+ self.progressView?.progress = 0
927
+ if let url = webView.url {
928
+ self.url = url
929
+ delegate?.webViewController?(self, didFail: url, withError: error)
930
+ }
931
+ self.capBrowserPlugin?.notifyListeners("pageLoadError", data: [:])
932
+ }
933
+
934
+ public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
935
+ updateBarButtonItems()
936
+ self.progressView?.progress = 0
937
+ if let url = webView.url {
938
+ self.url = url
939
+ delegate?.webViewController?(self, didFail: url, withError: error)
940
+ }
941
+ self.capBrowserPlugin?.notifyListeners("pageLoadError", data: [:])
942
+ }
943
+
944
+ public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
945
+ if let credentials = credentials,
946
+ challenge.protectionSpace.receivesCredentialSecurely,
947
+ let url = webView.url, challenge.protectionSpace.host == url.host, challenge.protectionSpace.protocol == url.scheme, challenge.protectionSpace.port == url.port ?? (url.scheme == "https" ? 443 : url.scheme == "http" ? 80 : nil) {
948
+ let urlCredential = URLCredential(user: credentials.username, password: credentials.password, persistence: .none)
949
+ completionHandler(.useCredential, urlCredential)
950
+ } else if let bypassedSSLHosts = bypassedSSLHosts, bypassedSSLHosts.contains(challenge.protectionSpace.host) {
951
+ let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
952
+ completionHandler(.useCredential, credential)
953
+ } else {
954
+ guard self.ignoreUntrustedSSLError else {
955
+ completionHandler(.performDefaultHandling, nil)
956
+ return
957
+ }
958
+ /* allows to open links with self-signed certificates
959
+ Follow Apple's guidelines https://developer.apple.com/documentation/foundation/url_loading_system/handling_an_authentication_challenge/performing_manual_server_trust_authentication
960
+ */
961
+ guard let serverTrust = challenge.protectionSpace.serverTrust else {
962
+ completionHandler(.useCredential, nil)
963
+ return
964
+ }
965
+ let credential = URLCredential(trust: serverTrust)
966
+ completionHandler(.useCredential, credential)
967
+ }
968
+ self.injectJavaScriptInterface()
969
+ }
970
+
971
+ public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
972
+ var actionPolicy: WKNavigationActionPolicy = .allow
973
+
974
+ if self.preventDeeplink {
975
+ actionPolicy = .preventDeeplinkActionPolicy
976
+ }
977
+
978
+ guard let u = navigationAction.request.url else {
979
+ decisionHandler(actionPolicy)
980
+ return
981
+ }
982
+
983
+ // Check if the URL is an App Store URL
984
+ if u.absoluteString.contains("apps.apple.com") {
985
+ UIApplication.shared.open(u, options: [:], completionHandler: nil)
986
+ // Cancel the navigation in the web view
987
+ decisionHandler(.cancel)
988
+ return
989
+ }
990
+
991
+ if !self.allowsFileURL && u.isFileURL {
992
+ print("Cannot handle file URLs")
993
+ decisionHandler(.cancel)
994
+ return
995
+ }
996
+
997
+ if handleURLWithApp(u, targetFrame: navigationAction.targetFrame) {
998
+ actionPolicy = .cancel
999
+ }
1000
+
1001
+ if u.host == self.source?.url?.host, let cookies = availableCookies, !checkRequestCookies(navigationAction.request, cookies: cookies) {
1002
+ self.load(remote: u)
1003
+ actionPolicy = .cancel
1004
+ }
1005
+
1006
+ if let navigationType = NavigationType(rawValue: navigationAction.navigationType.rawValue), let result = delegate?.webViewController?(self, decidePolicy: u, navigationType: navigationType) {
1007
+ actionPolicy = result ? .allow : .cancel
1008
+ }
1009
+ self.injectJavaScriptInterface()
1010
+ decisionHandler(actionPolicy)
1011
+ }
1012
+ }
1013
+
1014
+ class BlockBarButtonItem: UIBarButtonItem {
1015
+
1016
+ var block: ((WKWebViewController) -> Void)?
1017
+ }
1018
+
1019
+ extension WKNavigationActionPolicy {
1020
+ static let preventDeeplinkActionPolicy = WKNavigationActionPolicy(rawValue: WKNavigationActionPolicy.allow.rawValue + 2)!
1021
+ }