|
@@ -0,0 +1,872 @@
|
|
|
|
+//
|
|
|
|
+// StatusUpdateVC.swift
|
|
|
|
+// AppBuilder
|
|
|
|
+//
|
|
|
|
+// Created by Qindi on 16/07/25.
|
|
|
|
+//
|
|
|
|
+import UIKit
|
|
|
|
+import WebKit
|
|
|
|
+import NexilisLite
|
|
|
|
+import Speech
|
|
|
|
+import CommonCrypto
|
|
|
|
+
|
|
|
|
+class StatusUpdateVC: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate, WKScriptMessageHandler, ImageVideoPickerDelegate {
|
|
|
|
+
|
|
|
|
+ var webView: WKWebView!
|
|
|
|
+ var address = ""
|
|
|
|
+ private var lastContentOffset: CGFloat = 0
|
|
|
|
+
|
|
|
|
+ var isAllowSpeech = false
|
|
|
|
+
|
|
|
|
+ let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "id"))
|
|
|
|
+
|
|
|
|
+ var recognitionRequest : SFSpeechAudioBufferRecognitionRequest?
|
|
|
|
+ var recognitionTask : SFSpeechRecognitionTask?
|
|
|
|
+ let audioEngine = AVAudioEngine()
|
|
|
|
+ var alertController = LibAlertController()
|
|
|
|
+
|
|
|
|
+ public static var forceRefresh = true
|
|
|
|
+ public static var canLoadURL = false
|
|
|
|
+ public static var showModal = false
|
|
|
|
+
|
|
|
|
+ var indexImageVideoWv = 0
|
|
|
|
+ var imageVideoPicker: ImageVideoPicker!
|
|
|
|
+ var blockedCertificate = ""
|
|
|
|
+ var allowedURLs = Set<String>()
|
|
|
|
+ var loadingURL = false
|
|
|
|
+
|
|
|
|
+ override func viewDidLoad() {
|
|
|
|
+ super.viewDidLoad()
|
|
|
|
+
|
|
|
|
+ self.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .black : .white
|
|
|
|
+
|
|
|
|
+ let configuration = WKWebViewConfiguration()
|
|
|
|
+ configuration.allowsInlineMediaPlayback = true
|
|
|
|
+ loadContentBlocker(into: configuration) { [self] in
|
|
|
|
+ DispatchQueue.main.async {
|
|
|
|
+ self.initializeWebView(with: configuration)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ func initializeWebView(with configuration: WKWebViewConfiguration) {
|
|
|
|
+ let tapGesture = UITapGestureRecognizer(target: self, action: #selector(collapseDocked))
|
|
|
|
+ tapGesture.cancelsTouchesInView = false
|
|
|
|
+ tapGesture.delegate = self
|
|
|
|
+ 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.navigationDelegate = 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)
|
|
|
|
+ NotificationCenter.default.addObserver(self, selector: #selector(onResumeWebView(notification:)), name: UIApplication.willEnterForegroundNotification, object: nil)
|
|
|
|
+ StatusUpdateVC.canLoadURL = true
|
|
|
|
+ processURL()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ func loadContentBlocker(into config: WKWebViewConfiguration, completion: @escaping () -> Void) {
|
|
|
|
+ // Define ad-blocking rules directly in Swift as a string
|
|
|
|
+ let contentRules = PrefsUtil.contentRulesAds
|
|
|
|
+
|
|
|
|
+ WKContentRuleListStore.default().compileContentRuleList(forIdentifier: "AdBlocker", encodedContentRuleList: contentRules) { ruleList, error in
|
|
|
|
+ if let ruleList = ruleList {
|
|
|
|
+ config.userContentController.add(ruleList)
|
|
|
|
+ } else {
|
|
|
|
+ print("Failed to compile content rule list: \(error?.localizedDescription ?? "Unknown error")")
|
|
|
|
+ }
|
|
|
|
+ completion()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 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)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ func processURL() {
|
|
|
|
+ let me = User.getMyPin()
|
|
|
|
+
|
|
|
|
+ var myURL : URL?
|
|
|
|
+ let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
|
|
|
|
+ var intLang = 0
|
|
|
|
+ if lang == "id" {
|
|
|
|
+ intLang = 1
|
|
|
|
+ }
|
|
|
|
+ if PrefsUtil.getURLStatusUpdate() != nil {
|
|
|
|
+ ViewController.sURL = PrefsUtil.getURLStatusUpdate()!
|
|
|
|
+ }
|
|
|
|
+ switch(ViewController.sURL){
|
|
|
|
+ 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.sURL.isEmpty){
|
|
|
|
+ if ViewController.sURL.contains("?f_pin="){
|
|
|
|
+ address = "\(ViewController.sURL)\(me ?? "")"
|
|
|
|
+ } else {
|
|
|
|
+ address = "\(ViewController.sURL)?f_pin=\(me ?? "")"
|
|
|
|
+ }
|
|
|
|
+ myURL = URL(string: address)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if let u = myURL {
|
|
|
|
+ webView.evaluateJavaScript("{window.localStorage.setItem('currentTab','\(ViewController.sURL)')}")
|
|
|
|
+ if StatusUpdateVC.forceRefresh {
|
|
|
|
+ loadURLWithCookie(url: u)
|
|
|
|
+ } else {
|
|
|
|
+ webView.evaluateJavaScript("if(resumeAll){resumeAll();}")
|
|
|
|
+ }
|
|
|
|
+ StatusUpdateVC.forceRefresh = false
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ override func viewWillAppear(_ animated: Bool) {
|
|
|
|
+ if StatusUpdateVC.canLoadURL {
|
|
|
|
+ processURL()
|
|
|
|
+ }
|
|
|
|
+ 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: {
|
|
|
|
+ var viewController = UIApplication.shared.windows.first!.rootViewController
|
|
|
|
+ if let nc = viewController as? UINavigationController {
|
|
|
|
+ viewController = nc.viewControllers.first
|
|
|
|
+ }
|
|
|
|
+ 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) {
|
|
|
|
+ StatusUpdateVC.forceRefresh = true
|
|
|
|
+ StatusUpdateVC.showModal = false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @objc func onResumeWebView(notification: NSNotification) {
|
|
|
|
+ Nexilis.reloadCookies(webView: webView)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ override func viewWillDisappear(_ animated: Bool) {
|
|
|
|
+ if self.webView != nil {
|
|
|
|
+ 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();}}")
|
|
|
|
+ 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) {
|
|
|
|
+// StatusUpdateVC.forceRefresh = true
|
|
|
|
+// viewWillAppear(false)
|
|
|
|
+ webView.reload()
|
|
|
|
+ ViewController.alwaysHideButton = false
|
|
|
|
+ showTabBar()
|
|
|
|
+ sender.endRefreshing()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ func hideTabBar() {
|
|
|
|
+ if UIApplication.shared.windows.first == nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ var viewController = UIApplication.shared.windows.first!.rootViewController
|
|
|
|
+ if let nc = viewController as? UINavigationController {
|
|
|
|
+ viewController = nc.viewControllers.first
|
|
|
|
+ }
|
|
|
|
+ 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 let nc = viewController as? UINavigationController {
|
|
|
|
+ viewController = nc.viewControllers.first
|
|
|
|
+ }
|
|
|
|
+ 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)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ StatusUpdateVC.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
|
|
|
|
+ }
|
|
|
|
+ if loadingURL {
|
|
|
|
+ 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.getURLStatusUpdate() == "0" || PrefsUtil.getURLStatusUpdate() == "1" || PrefsUtil.getURLStatusUpdate() == "2" || PrefsUtil.getURLStatusUpdate() == "3" || PrefsUtil.getURLStatusUpdate() == "4"
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+extension StatusUpdateVC: SFSpeechRecognizerDelegate {
|
|
|
|
+
|
|
|
|
+ func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) {
|
|
|
|
+ if available {
|
|
|
|
+ self.isAllowSpeech = true
|
|
|
|
+ } else {
|
|
|
|
+ self.isAllowSpeech = false
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+extension StatusUpdateVC: WKUIDelegate, WKNavigationDelegate {
|
|
|
|
+ func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) {
|
|
|
|
+ guard let url = navigationAction.request.url else {
|
|
|
|
+ decisionHandler(.cancel)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if let scheme = url.scheme?.lowercased(), scheme.hasPrefix("itms") || scheme == "mailto" || scheme == "tel" {
|
|
|
|
+ DispatchQueue.main.async {
|
|
|
|
+ UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
|
|
|
+ }
|
|
|
|
+ decisionHandler(.cancel)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ guard navigationAction.targetFrame?.isMainFrame == true else {
|
|
|
|
+ decisionHandler(.allow)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if loadingURL {
|
|
|
|
+ decisionHandler(.cancel)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ loadingURL = true
|
|
|
|
+
|
|
|
|
+ if allowedURLs.contains(url.absoluteString) {
|
|
|
|
+ loadingURL = false
|
|
|
|
+ decisionHandler(.allow)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ validateSSLCertificate(url: url) { [weak self] isValid in
|
|
|
|
+ guard let self = self else {
|
|
|
|
+ decisionHandler(.cancel)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ DispatchQueue.main.async {
|
|
|
|
+ if isValid {
|
|
|
|
+ self.allowedURLs.insert(url.absoluteString)
|
|
|
|
+ self.loadingURL = false
|
|
|
|
+ decisionHandler(.allow)
|
|
|
|
+ } else {
|
|
|
|
+ let host = url.host ?? ""
|
|
|
|
+ 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 <<domain>> 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: "<<domain>>", with: host)
|
|
|
|
+
|
|
|
|
+ let alert = UIAlertController(title: "Warning Unknown Url!".localized(),
|
|
|
|
+ message: messageText,
|
|
|
|
+ preferredStyle: .alert)
|
|
|
|
+
|
|
|
|
+ alert.addAction(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)
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ alert.addAction(UIAlertAction(title: "No", style: .cancel) { _ in
|
|
|
|
+ self.loadingURL = false
|
|
|
|
+ decisionHandler(.cancel)
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ if self.presentedViewController == nil {
|
|
|
|
+ self.present(alert, animated: true, completion: nil)
|
|
|
|
+ } else {
|
|
|
|
+ self.loadingURL = false
|
|
|
|
+ decisionHandler(.cancel)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 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 StatusUpdateVC: 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<CFError>?
|
|
|
|
+ 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
|
|
|
|
+ }
|
|
|
|
+}
|