// // ThirdTabViewController.swift // AppBuilder // // Created by Kevin Maulana on 30/03/22. // import UIKit import WebKit import NexilisLite import Speech import CommonCrypto class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate, WKScriptMessageHandler, ImageVideoPickerDelegate { var webView: WKWebView! var address = "" private var lastContentOffset: CGFloat = 0 var isAllowSpeech = false var speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "en")) var recognitionRequest : SFSpeechAudioBufferRecognitionRequest? var recognitionTask : SFSpeechRecognitionTask? let audioEngine = AVAudioEngine() var alertController = LibAlertController() public static var forceRefresh = true public static var inView = false public static var atFirstPage = true public static var showModal = false var indexImageVideoWv = 0 var imageVideoPicker: ImageVideoPicker! var blockedCertificate = "" var allowedURLs = Set() var loadingURL = false override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .black : .white let tapGesture = UITapGestureRecognizer(target: self, action: #selector(collapseDocked)) tapGesture.cancelsTouchesInView = false tapGesture.delegate = self let configuration = WKWebViewConfiguration() configuration.allowsInlineMediaPlayback = true let customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1 \(Utils.getUserAgent())" let finalUserAgent = "\(customUserAgent)" configuration.applicationNameForUserAgent = finalUserAgent webView = WKWebView(frame: .zero, configuration: configuration) view.addSubview(webView) webView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), webView.leadingAnchor.constraint(equalTo: view.leadingAnchor), webView.trailingAnchor.constraint(equalTo: view.trailingAnchor), webView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) webView.scrollView.addGestureRecognizer(tapGesture) let refreshControl = UIRefreshControl() refreshControl.addTarget(self, action: #selector(reloadWebView(_:)), for: .valueChanged) webView.scrollView.addSubview(refreshControl) webView.scrollView.delegate = self webView.allowsBackForwardNavigationGestures = true let contentController = self.webView.configuration.userContentController contentController.add(self, name: "checkProfile") contentController.add(self, name: "setIsProductModalOpen") contentController.add(self, name: "toggleVoiceSearch") contentController.add(self, name: "blockUser") contentController.add(self, name: "showAlert") contentController.add(self, name: "closeProfile") contentController.add(self, name: "tabShowHide") contentController.add(self, name: "shareText") contentController.add(self, name: "openGalleryiOS") let source: String = "var meta = document.createElement('meta');" + "meta.name = 'viewport';" + "meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';" + "var head = document.getElementsByTagName('head')[0];" + "head.appendChild(meta);" + "$('#header-layout').find('.col-8').removeClass('col-8').addClass('col');" + "$('#header-layout').find('.col-4').removeClass('col-4').addClass('col');" let script: WKUserScript = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: false) contentController.addUserScript(script) let cookieScript1 = "document.cookie = '\(Utils.getCookiesMobile().components(separatedBy: ";")[0])';" let cookieScriptInjection1 = WKUserScript(source: cookieScript1, injectionTime: .atDocumentStart, forMainFrameOnly: false) configuration.userContentController.addUserScript(cookieScriptInjection1) let cookieScript2 = "document.cookie = '\(Utils.getCookiesMobile().components(separatedBy: ";")[1])';" let cookieScriptInjection2 = WKUserScript(source: cookieScript2, injectionTime: .atDocumentStart, forMainFrameOnly: false) configuration.userContentController.addUserScript(cookieScriptInjection2) NotificationCenter.default.addObserver(self, selector: #selector(onShowAC(notification:)), name: NSNotification.Name(rawValue: "onShowAC"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(onRefreshWebView(notification:)), name: NSNotification.Name(rawValue: "onRefreshWebView"), object: nil) imageVideoPicker = ImageVideoPicker(presentationController: self, delegate: self) } func loadURLWithCookie(url: URL) { var urlRequest = URLRequest(url: url) let customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1 \(Utils.getUserAgent())" urlRequest.setValue(customUserAgent, forHTTPHeaderField: "User-Agent") if let cookies = HTTPCookieStorage.shared.cookies(for: url) { for cookie in cookies { webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie) } webView.load(urlRequest) } } override func viewWillAppear(_ animated: Bool) { if ThirdTabViewController.inView { return } ThirdTabViewController.inView = true let me = User.getMyPin() var myURL : URL? let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en" var intLang = 0 if lang == "id" { speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "id")) intLang = 1 } if PrefsUtil.getURLThirdTab() != nil { ViewController.tab3 = PrefsUtil.getURLThirdTab()! } switch(ViewController.tab3){ case "0": address = "\(PrefsUtil.getURLBase())nexilis/pages/tab1-main-only?f_pin=\(me ?? "")&lang=\(intLang)&theme=\(self.traitCollection.userInterfaceStyle == .dark ? "0" : "1")" myURL = URL(string: address) case "1": address = "\(PrefsUtil.getURLBase())nexilis/pages/tab3-main-only?f_pin=\(me ?? "")&lang=\(intLang)&theme=\(self.traitCollection.userInterfaceStyle == .dark ? "0" : "1")" myURL = URL(string: address) case "2": address = "\(PrefsUtil.getURLBase())nexilis/pages/tab1-main?f_pin=\(me ?? "")&lang=\(intLang)&theme=\(self.traitCollection.userInterfaceStyle == .dark ? "0" : "1")" myURL = URL(string: address) case "3": address = "\(PrefsUtil.getURLBase())nexilis/pages/tab3-commerce?f_pin=\(me ?? "")&lang=\(intLang)&theme=\(self.traitCollection.userInterfaceStyle == .dark ? "0" : "1")" myURL = URL(string: address) case "4": address = "\(PrefsUtil.getURLBase())nexilis/pages/tab1-video?f_pin=\(me ?? "")&lang=\(intLang)&theme=\(self.traitCollection.userInterfaceStyle == .dark ? "0" : "1")" myURL = URL(string: address) default: if(!ViewController.tab3.isEmpty){ if(ViewController.tab3.lowercased().contains("https://") || ViewController.sURL.lowercased().contains("http://")){ address = ViewController.tab3 myURL = URL(string: address) } else { if ViewController.tab3.contains("nexilis/pages"){ address = "\(PrefsUtil.getURLBase())\(ViewController.tab3)?f_pin=\(me ?? "")&lang=\(intLang)&theme=\(self.traitCollection.userInterfaceStyle == .dark ? "0" : "1")" } else { address = "https://\(ViewController.tab3)" } myURL = URL(string: address) } } } //print(address) if let u = myURL{ self.webView.evaluateJavaScript("{window.localStorage.setItem('currentTab','\(ViewController.tab3)')}") if ThirdTabViewController.forceRefresh { loadURLWithCookie(url: u) } else { self.webView.evaluateJavaScript("if(resumeAll){resumeAll();}") } ThirdTabViewController.forceRefresh = false } navigationController?.setNavigationBarHidden(true, animated: false) } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { if Utils.getIsLoadThemeFromOther() { self.webView.evaluateJavaScript("{window.localStorage.setItem('mobileConfiguration','"+Utils.getMyTheme()+"')}") } } override func viewDidAppear(_ animated: Bool) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: { // if (self.isUsingMyWebview() && self.webView.url != nil && !self.webView.url!.absoluteString.contains("nexilis/pages/tab1-main-only") && !self.webView.url!.absoluteString.contains("nexilis/pages/tab3-main-only") && !self.webView.url!.absoluteString.contains("nexilis/pages/tab1-main") && !self.webView.url!.absoluteString.contains("nexilis/pages/tab3-commerce") && !self.webView.url!.absoluteString.contains("nexilis/pages/tab1-video") && !self.webView.url!.absoluteString.contains("nexilis/pages/tab3-main")) || ThirdTabViewController.showModal { // ViewController.alwaysHideButton = true // self.hideTabBar() // ThirdTabViewController.atFirstPage = false // } else { var viewController = UIApplication.shared.windows.first!.rootViewController if !(viewController is ViewController) { viewController = self.parent } if ViewController.middleButton.isHidden { ViewController.isExpandButton = false if let viewController = viewController as? ViewController { if viewController.tabBar.isHidden { viewController.tabBar.isHidden = false ViewController.middleButton.isHidden = false ViewController.alwaysHideButton = false } } } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED { DispatchQueue.main.async { if let viewController = viewController as? ViewController { if viewController.tabBar.isHidden { viewController.tabBar.isHidden = false ViewController.alwaysHideButton = false } } } } // } }) } @objc func onShowAC(notification: NSNotification) { self.webView.evaluateJavaScript("{if(pauseAll){pauseAll();}}") view.endEditing(true) resignFirstResponder() } @objc func onRefreshWebView(notification: NSNotification) { ThirdTabViewController.forceRefresh = true } override func viewWillDisappear(_ animated: Bool) { if self.webView.scrollView.contentOffset.y < 0 { // Move tableView to top self.webView.scrollView.setContentOffset(CGPoint.zero, animated: true) } self.webView.evaluateJavaScript("{if(pauseAll){pauseAll();}}") view.endEditing(true) resignFirstResponder() ThirdTabViewController.inView = false self.webView.evaluateJavaScript("hideAddToCart();") } func scrollViewDidScroll(_ scrollView: UIScrollView) { if (self.lastContentOffset > scrollView.contentOffset.y && scrollView.contentOffset.y < (scrollView.contentSize.height - scrollView.frame.size.height)) { showTabBar(); } else if (self.lastContentOffset != 0 && self.lastContentOffset < scrollView.contentOffset.y && self.lastContentOffset >= 0) { hideTabBar(); } self.lastContentOffset = scrollView.contentOffset.y self.collapseDocked() } @objc func collapseDocked() { if ViewController.isExpandButton { ViewController.expandButton() } } @objc func reloadWebView(_ sender: UIRefreshControl) { webView.reload() ViewController.alwaysHideButton = false showTabBar() sender.endRefreshing() } func hideTabBar() { var viewController = UIApplication.shared.windows.first!.rootViewController if !(viewController is ViewController) { viewController = self.parent } if ViewController.middleButton.isDescendant(of: viewController!.view) { DispatchQueue.main.async { if ViewController.isExpandButton { ViewController.expandButton() } ViewController.hideDockedButton() if let viewController = viewController as? ViewController { viewController.tabBar.isHidden = true } ViewController.removeMiddleButton() } } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED { DispatchQueue.main.async { if let viewController = viewController as? ViewController { if !viewController.tabBar.isHidden { viewController.tabBar.isHidden = true } } } } } func showTabBar() { if(ViewController.alwaysHideButton){ return } var viewController = UIApplication.shared.windows.first!.rootViewController if !(viewController is ViewController) { viewController = self.parent } if ViewController.middleButton.isHidden { if let viewController = viewController as? ViewController { viewController.tabBar.isHidden = false ViewController.middleButton.isHidden = false } } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED { DispatchQueue.main.async { if let viewController = viewController as? ViewController { if viewController.tabBar.isHidden { viewController.tabBar.isHidden = false } } } } } func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) { scrollView.pinchGestureRecognizer?.isEnabled = false } func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { if message.name == "checkProfile" { guard let dict = message.body as? [String: AnyObject], let param1 = dict["param1"] as? String, let param2 = dict["param2"] as? String else { return } if ViewController.checkIsChangePerson() { if param2 == "like" { self.webView.evaluateJavaScript("likeProduct('\(param1)',1,true);") } else if param2 == "comment" { self.webView.evaluateJavaScript("openComment('\(param1.split(separator: "|")[0])',\(param1.split(separator: "|")[1]),true);") } else if param2 == "report_user" { self.webView.evaluateJavaScript("reportUser('\(param1)',true);") } else if param2 == "report_content" { self.webView.evaluateJavaScript("reportContent('\(param1.split(separator: "|")[0])','\(param1.split(separator: "|")[1])',true);") } else if param2 == "block_user" { self.webView.evaluateJavaScript("blockUser('\(param1)',true);") } else if param2 == "follow_user" { self.webView.evaluateJavaScript("followUser('\(param1.split(separator: "|")[0])',\(param1.split(separator: "|")[1]),true);") } else if param2 == "homepage" || param2 == "gif" { self.webView.evaluateJavaScript("window.location.href = '\(param1)';") } else if param2 == "block_content" { self.webView.evaluateJavaScript("blockContent('\(param1)',true);") } else { self.webView.evaluateJavaScript("openNewPost(true);") } } else { self.webView.evaluateJavaScript("{if(pauseAll){pauseAll();}}") } } else if message.name == "setIsProductModalOpen" { guard let dict = message.body as? [String: AnyObject], let param1 = dict["param1"] as? Bool else { return } if param1 { if self.webView.scrollView.contentOffset.y < 0 { // Move tableView to top self.webView.scrollView.setContentOffset(CGPoint.zero, animated: true) } } ThirdTabViewController.showModal = param1 } else if message.name == "toggleVoiceSearch" { if !isAllowSpeech { setupSpeech() } else { runVoice() } } else if message.name == "blockUser" { guard let dict = message.body as? [String: AnyObject], let param1 = dict["param1"] as? String, let param2 = dict["param2"] as? Bool else { return } if param2 { DispatchQueue.global().async { if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getBlock(l_pin: param1)) { if response.isOk() { DispatchQueue.main.async { Database.shared.database?.inTransaction({ (fmdb, rollback) in _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [ "ex_block" : "1" ], _where: "f_pin = '\(param1)'") }) } } } } } else { DispatchQueue.global().async { if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getUnBlock(l_pin: param1)) { if response.isOk() { DispatchQueue.main.async { Database.shared.database?.inTransaction({ (fmdb, rollback) in _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [ "ex_block" : "0" ], _where: "f_pin = '\(param1)'") }) } } } } } } else if message.name == "showAlert" { guard let dict = message.body as? [String: AnyObject], let param1 = dict["param1"] as? String else { return } self.view.makeToast(param1, duration: 3) } else if message.name == "blockUser" { guard let dict = message.body as? [String: AnyObject], let param1 = dict["param1"] as? String, let param2 = dict["param2"] as? Bool else { return } if param2 { DispatchQueue.global().async { if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getBlock(l_pin: param1)) { if response.isOk() { DispatchQueue.main.async { Database.shared.database?.inTransaction({ (fmdb, rollback) in _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [ "ex_block" : "1" ], _where: "f_pin = '\(param1)'") }) } } } } } else { DispatchQueue.global().async { if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getUnBlock(l_pin: param1)) { if response.isOk() { DispatchQueue.main.async { Database.shared.database?.inTransaction({ (fmdb, rollback) in _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [ "ex_block" : "0" ], _where: "f_pin = '\(param1)'") }) } } } } } } else if message.name == "tabShowHide" { guard let dict = message.body as? [String: AnyObject], let param1 = dict["param1"] as? Bool else { return } if param1 { ViewController.alwaysHideButton = false showTabBar() } else { if self.viewIfLoaded?.window != nil { ViewController.alwaysHideButton = true hideTabBar() } } } else if message.name == "shareText" { guard let dict = message.body as? [String: AnyObject], let param1 = dict["param1"] as? String else { return } let activityViewController = UIActivityViewController(activityItems: [param1], applicationActivities: nil) self.present(activityViewController, animated: true, completion: nil) } else if message.name == "openGalleryiOS" { guard let dict = message.body as? [String: AnyObject], let param1 = dict["param1"] as? Int else { return } indexImageVideoWv = param1 let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet) if let action = self.actionImageVideo(for: "image", title: "Choose Photo".localized()) { alertController.addAction(action) } if let action = self.actionImageVideo(for: "video", title: "Choose Video".localized()) { alertController.addAction(action) } alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil)) self.present(alertController, animated: true) } } private func actionImageVideo(for type: String, title: String) -> UIAlertAction? { return UIAlertAction(title: title, style: .default) { [unowned self] _ in switch type { case "image": imageVideoPicker.present(source: .imageAlbum) case "video": imageVideoPicker.present(source: .videoAlbum) default: imageVideoPicker.present(source: .imageAlbum) } } } func didSelect(imagevideo: Any?) { if imagevideo != nil { let imageData = imagevideo! as! [UIImagePickerController.InfoKey : Any] if (imageData[.mediaType] as! String == "public.image") { let compressedImage = (imageData[.originalImage] as! UIImage).pngData()! let base64String = compressedImage.base64EncodedString() let base64ToWeb = "data:image/jpeg;base64,\(base64String)" webView.evaluateJavaScript("loadFromMobile('\(base64ToWeb)',\(indexImageVideoWv))") { (result, error) in if let error = error { print("Error executing JavaScript: \(error)") } } } else { guard var dataVideo = try? Data(contentsOf: imageData[.mediaURL] as! URL) else { return } let sizeOfVideo = Double(dataVideo.count / 1048576) if (sizeOfVideo > 10.0) { let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4") compressVideo(inputURL: imageData[.mediaURL] as! URL, outputURL: compressedURL) { exportSession in guard let session = exportSession else { return } switch session.status { case .unknown: break case .waiting: break case .exporting: break case .completed: guard let compressedData = try? Data(contentsOf: compressedURL) else { return } dataVideo = compressedData case .failed: break case .cancelled: break @unknown default: break } } } let base64String = dataVideo.base64EncodedString() let base64ToWeb = "data:video/mp4;base64,\(base64String)" webView.evaluateJavaScript("loadFromMobile('\(base64ToWeb)',\(indexImageVideoWv))") { (result, error) in if let error = error { print("Error executing JavaScript: \(error)") } } } } } func compressVideo(inputURL: URL, outputURL: URL, handler:@escaping (_ exportSession: AVAssetExportSession?) -> Void) { let urlAsset = AVURLAsset(url: inputURL, options: nil) guard let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetMediumQuality) else { handler(nil) return } exportSession.outputURL = outputURL exportSession.outputFileType = .mp4 exportSession.exportAsynchronously { handler(exportSession) } } func setupSpeech() { self.speechRecognizer?.delegate = self SFSpeechRecognizer.requestAuthorization { (authStatus) in var isButtonEnabled = false switch authStatus { case .authorized: isButtonEnabled = true case .denied: isButtonEnabled = false //print("User denied access to speech recognition") case .restricted: isButtonEnabled = false //print("Speech recognition restricted on this device") case .notDetermined: isButtonEnabled = false //print("Speech recognition not yet authorized") @unknown default: isButtonEnabled = false } OperationQueue.main.addOperation() { self.isAllowSpeech = isButtonEnabled if isButtonEnabled { SecureUserDefaults.shared.set(isButtonEnabled, forKey: "allowSpeech") self.runVoice() } } } } func runVoice() { if !audioEngine.isRunning { alertController = LibAlertController(title: "Start Recording".localized(), message: "Say something, I'm listening!".localized(), preferredStyle: .alert) self.present(alertController, animated: true) self.webView.evaluateJavaScript("toggleVoiceButton(true)") self.startRecording() } } func startRecording() { // Clear all previous session data and cancel task if recognitionTask != nil { recognitionTask?.cancel() recognitionTask = nil } // Create instance of audio session to record voice let audioSession = AVAudioSession.sharedInstance() do { try audioSession.setCategory(AVAudioSession.Category.record, mode: .default, options: []) try audioSession.setMode(AVAudioSession.Mode.measurement) try audioSession.setActive(true, options: .notifyOthersOnDeactivation) } catch { //print("audioSession properties weren't set because of an error.") } self.recognitionRequest = SFSpeechAudioBufferRecognitionRequest() let inputNode = audioEngine.inputNode guard let recognitionRequest = recognitionRequest else { fatalError("Unable to create an SFSpeechAudioBufferRecognitionRequest object") } recognitionRequest.shouldReportPartialResults = true self.recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest, resultHandler: { (result, error) in var isFinal = false var text = "" if result != nil { text = result?.bestTranscription.formattedString ?? "" isFinal = (result?.isFinal)! self.alertController.dismiss(animated: true) self.audioEngine.stop() self.recognitionRequest?.endAudio() } else { self.alertController.dismiss(animated: true) } if error != nil || isFinal { if error == nil { self.webView.evaluateJavaScript("toggleVoiceButton(false)") self.webView.evaluateJavaScript("submitVoiceSearch('\(text)')") } else { self.audioEngine.stop() self.recognitionRequest?.endAudio() } inputNode.removeTap(onBus: 0) self.recognitionRequest = nil self.recognitionTask = nil self.isAllowSpeech = true } }) let recordingFormat = inputNode.outputFormat(forBus: 0) inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer, when) in self.recognitionRequest?.append(buffer) } self.audioEngine.prepare() do { try self.audioEngine.start() } catch { //print("audioEngine couldn't start because of an error.") } } func isUsingMyWebview() -> Bool{ return PrefsUtil.getURLThirdTab() == "0" || PrefsUtil.getURLThirdTab() == "1" || PrefsUtil.getURLThirdTab() == "2" || PrefsUtil.getURLThirdTab() == "3" || PrefsUtil.getURLThirdTab() == "4" } } extension ThirdTabViewController: SFSpeechRecognizerDelegate { func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) { if available { self.isAllowSpeech = true } else { self.isAllowSpeech = false } } } extension ThirdTabViewController: WKUIDelegate { func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) { guard let url = navigationAction.request.url else { decisionHandler(.cancel) return } if loadingURL { decisionHandler(.cancel) return } loadingURL = true if allowedURLs.contains(url.absoluteString) { if ViewController.alwaysHideButton { ViewController.alwaysHideButton = false showTabBar() } loadingURL = false decisionHandler(.allow) return } validateSSLCertificate(url: url) { isValid in if isValid { self.allowedURLs.insert(url.absoluteString) self.loadingURL = false decisionHandler(.allow) } else { let host = url.host ?? "" DispatchQueue.main.async { var messageText = "You're about to access a website that is not currently trusted by your Nexilis Browser. This website's security certificate is not recognized.\n\nDo you wish to proceed to <> and trust the website's security certificate?\n\nNote: Adding a website to the trusted list may increase your risk of security vulnerability".localized() messageText = messageText.replacingOccurrences(of: "<>", with: host) let alert = UIAlertController(title: "Warning Unknown Url!".localized(), message: messageText, preferredStyle: .alert) let yesAction = UIAlertAction(title: "Yes", style: .default) { _ in let storedCertificate = Utils.getCertificatePinningWebview() if let jsonData = storedCertificate.data(using: .utf8), let certJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] { var certJson = certJson certJson[host] = self.blockedCertificate if let jsonData = try? JSONSerialization.data(withJSONObject: certJson, options: []), let jsonString = String(data: jsonData, encoding: .utf8) { Utils.setCertificatePinningWebview(value: jsonString) } } self.allowedURLs.insert(url.absoluteString) self.loadingURL = false decisionHandler(.allow) } let noAction = UIAlertAction(title: "No", style: .cancel) { _ in self.loadingURL = false decisionHandler(.cancel) } alert.addAction(yesAction) alert.addAction(noAction) self.present(alert, animated: true, completion: nil) } } } } private func validateSSLCertificate(url: URL, completion: @escaping (Bool) -> Void) { let session = URLSession(configuration: .ephemeral, delegate: self, delegateQueue: nil) let request = URLRequest(url: url) let task = session.dataTask(with: request) { _, response, error in if let error = error { completion(false) return } completion(true) } task.resume() } } extension ThirdTabViewController: URLSessionDelegate { func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { guard let serverTrust = challenge.protectionSpace.serverTrust else { completionHandler(.cancelAuthenticationChallenge, nil) return } if let publicKeyHash = extractPublicKeyHash(from: serverTrust) { let domain = challenge.protectionSpace.host let storedCertificate = Utils.getCertificatePinningWebview() if let jsonData = storedCertificate.data(using: .utf8), let certJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] { if publicKeyHash == certJson[domain] { completionHandler(.useCredential, URLCredential(trust: serverTrust)) } else { blockedCertificate = publicKeyHash completionHandler(.cancelAuthenticationChallenge, nil) } } } else { completionHandler(.cancelAuthenticationChallenge, nil) } } func extractPublicKeyHash(from serverTrust: SecTrust) -> String? { guard let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else { return nil } guard let publicKey = SecCertificateCopyKey(certificate) else { return nil } var error: Unmanaged? guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else { return nil } // Compute SHA-256 hash var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) publicKeyData.withUnsafeBytes { _ = CC_SHA256($0.baseAddress, CC_LONG(publicKeyData.count), &hash) } let hashData = Data(hash) let base64Hash = hashData.base64EncodedString() return base64Hash } }