BNIBookingWebView.swift 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004
  1. //
  2. // BNIBookingWebView.swift
  3. // FMDB
  4. //
  5. // Created by Qindi on 01/04/22.
  6. //
  7. import Foundation
  8. import UIKit
  9. import WebKit
  10. import Speech
  11. import CommonCrypto
  12. public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScrollViewDelegate, UIGestureRecognizerDelegate, WKScriptMessageHandler, SFSpeechRecognizerDelegate, ImageVideoPickerDelegate {
  13. var webView: WKWebView!
  14. let closeButton = UIButton()
  15. public var customUrl = ""
  16. public var isSecureBrowser = false
  17. let textField = UITextField()
  18. var isAllowSpeech = false
  19. let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "id"))
  20. var recognitionRequest : SFSpeechAudioBufferRecognitionRequest?
  21. var recognitionTask : SFSpeechRecognitionTask?
  22. let audioEngine = AVAudioEngine()
  23. var alertController = LibAlertController()
  24. var indexImageVideoWv = 0
  25. var imageVideoPicker: ImageVideoPicker!
  26. var blockedCertificate = ""
  27. var allowedURLs = Set<String>()
  28. var loadingURL = false
  29. public override var preferredStatusBarStyle: UIStatusBarStyle {
  30. return .default
  31. }
  32. public override func viewDidLoad() {
  33. super.viewDidLoad()
  34. let attributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
  35. let navBarAppearance = UINavigationBarAppearance()
  36. navBarAppearance.configureWithOpaqueBackground()
  37. navBarAppearance.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : UIColor.mainColor
  38. navBarAppearance.titleTextAttributes = attributes
  39. navigationController?.navigationBar.standardAppearance = navBarAppearance
  40. navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
  41. let backButton = UIBarButtonItem(image: UIImage(systemName: "chevron.backward"), style: .plain, target: self, action: #selector(self.didTapExit))
  42. self.navigationItem.leftBarButtonItem = backButton
  43. let configuration = WKWebViewConfiguration()
  44. configuration.allowsInlineMediaPlayback = true
  45. loadContentBlocker(into: configuration) { [self] in
  46. DispatchQueue.main.async {
  47. self.initializeWebView(with: configuration)
  48. }
  49. }
  50. }
  51. @objc func didTapExit(sender: Any) {
  52. self.dismiss(animated: true, completion: nil)
  53. }
  54. func initializeWebView(with configuration: WKWebViewConfiguration) {
  55. 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())"
  56. let finalUserAgent = "\(customUserAgent)"
  57. configuration.applicationNameForUserAgent = finalUserAgent
  58. webView = WKWebView(frame: .zero, configuration: configuration)
  59. let containerView = UIView()
  60. containerView.backgroundColor = .white
  61. if isSecureBrowser {
  62. title = "Secure Browser".localized()
  63. view.addSubview(containerView)
  64. containerView.translatesAutoresizingMaskIntoConstraints = false
  65. containerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
  66. containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
  67. containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
  68. containerView.heightAnchor.constraint(equalToConstant: 44).isActive = true
  69. containerView.addSubview(textField)
  70. textField.translatesAutoresizingMaskIntoConstraints = false
  71. textField.leftAnchor.constraint(equalTo: containerView.leftAnchor, constant: 10.0).isActive = true
  72. textField.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
  73. textField.heightAnchor.constraint(equalToConstant: 40).isActive = true
  74. textField.widthAnchor.constraint(equalToConstant: view.bounds.size.width - 80).isActive = true
  75. textField.layer.borderColor = UIColor.lightGray.cgColor
  76. textField.layer.borderWidth = 1.0
  77. textField.layer.cornerRadius = 5.0
  78. textField.clipsToBounds = true
  79. let buttonGo = UIButton(type: .custom)
  80. buttonGo.setTitle("Go".localized(), for: .normal)
  81. buttonGo.setTitleColor(.black, for: .normal)
  82. buttonGo.addTarget(self, action: #selector(goAction), for: .touchUpInside)
  83. containerView.addSubview(buttonGo)
  84. buttonGo.translatesAutoresizingMaskIntoConstraints = false
  85. buttonGo.leftAnchor.constraint(equalTo: textField.rightAnchor, constant: 10.0).isActive = true
  86. buttonGo.rightAnchor.constraint(equalTo: containerView.rightAnchor, constant: -10.0).isActive = true
  87. buttonGo.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
  88. buttonGo.heightAnchor.constraint(equalToConstant: 40).isActive = true
  89. }
  90. view.addSubview(webView)
  91. webView.translatesAutoresizingMaskIntoConstraints = false
  92. if isSecureBrowser {
  93. webView.topAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
  94. } else {
  95. webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
  96. }
  97. webView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
  98. webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
  99. webView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
  100. webView.navigationDelegate = self
  101. webView.allowsBackForwardNavigationGestures = true
  102. webView.scrollView.delegate = self
  103. let contentController = webView.configuration.userContentController
  104. contentController.add(self, name: "sendQueueBNI")
  105. contentController.add(self, name: "checkProfile")
  106. contentController.add(self, name: "setIsProductModalOpen")
  107. contentController.add(self, name: "toggleVoiceSearch")
  108. contentController.add(self, name: "blockUser")
  109. contentController.add(self, name: "showAlert")
  110. contentController.add(self, name: "closeProfile")
  111. contentController.add(self, name: "successChangeTheme")
  112. contentController.add(self, name: "finishForm")
  113. contentController.add(self, name: "shareText")
  114. contentController.add(self, name: "openGalleryiOS")
  115. let source: String = "var meta = document.createElement('meta');" +
  116. "meta.name = 'viewport';" +
  117. "meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';" +
  118. "var head = document.getElementsByTagName('head')[0];" +
  119. "head.appendChild(meta);" +
  120. "$('#header-layout').find('.col-8').removeClass('col-8').addClass('col');" +
  121. "$('#header-layout').find('.col-4').removeClass('col-4').addClass('col');"
  122. let script: WKUserScript = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
  123. contentController.addUserScript(script)
  124. let cookieScript1 = "document.cookie = '\(Utils.getCookiesMobile().components(separatedBy: ";")[0])';"
  125. let cookieScriptInjection1 = WKUserScript(source: cookieScript1, injectionTime: .atDocumentStart, forMainFrameOnly: false)
  126. configuration.userContentController.addUserScript(cookieScriptInjection1)
  127. let cookieScript2 = "document.cookie = '\(Utils.getCookiesMobile().components(separatedBy: ";")[1])';"
  128. let cookieScriptInjection2 = WKUserScript(source: cookieScript2, injectionTime: .atDocumentStart, forMainFrameOnly: false)
  129. configuration.userContentController.addUserScript(cookieScriptInjection2)
  130. let refreshControl = UIRefreshControl()
  131. refreshControl.addTarget(self, action: #selector(reloadWebView(_:)), for: .valueChanged)
  132. webView.scrollView.addSubview(refreshControl)
  133. webView.isOpaque = false
  134. webView.backgroundColor = .white
  135. webView.scrollView.backgroundColor = .white
  136. var stringQMS = "https://sqbni.murni.id:4200/bnibookingonline/#/?userid="
  137. if !customUrl.isEmpty {
  138. stringQMS = customUrl
  139. }
  140. if stringQMS.lowercased().contains("?userid=") {
  141. let name = User.getData(pin: User.getMyPin())!.fullName
  142. stringQMS += name
  143. } else if stringQMS.lowercased().contains("?f_pin=") {
  144. stringQMS += User.getMyPin()!
  145. }
  146. if stringQMS.contains("<<f_pin>>") {
  147. stringQMS = stringQMS.replacingOccurrences(of: "<<f_pin>>", with: User.getMyPin()!)
  148. }
  149. let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
  150. var intLang = 0
  151. if lang == "id" {
  152. intLang = 1
  153. }
  154. if stringQMS.contains("?") {
  155. stringQMS = stringQMS + "&lang=\(intLang)&theme=\(self.traitCollection.userInterfaceStyle == .dark ? "0" : "1")"
  156. } else {
  157. stringQMS = stringQMS + "?lang=\(intLang)&theme=\(self.traitCollection.userInterfaceStyle == .dark ? "0" : "1")"
  158. }
  159. if let url = URL(string: "\(stringQMS)") {
  160. if !isSecureBrowser {
  161. loadURLWithCookie(url: url)
  162. } else {
  163. if let url = URL(string: "https://google.com/") {
  164. loadURLWithCookie(url: url)
  165. }
  166. }
  167. } else if isSecureBrowser {
  168. if let url = URL(string: "https://google.com/") {
  169. loadURLWithCookie(url: url)
  170. }
  171. }
  172. }
  173. func loadContentBlocker(into config: WKWebViewConfiguration, completion: @escaping () -> Void) {
  174. // Define ad-blocking rules directly in Swift as a string
  175. let contentRules = #"""
  176. [
  177. {
  178. "trigger": {
  179. "url-filter": "doubleclick.net"
  180. },
  181. "action": {
  182. "type": "block"
  183. }
  184. },
  185. {
  186. "trigger": {
  187. "url-filter": "googlesyndication.com"
  188. },
  189. "action": {
  190. "type": "block"
  191. }
  192. },
  193. {
  194. "trigger": {
  195. "url-filter": "taboola.com"
  196. },
  197. "action": {
  198. "type": "block"
  199. }
  200. },
  201. {
  202. "trigger": {
  203. "url-filter": "outbrain.com"
  204. },
  205. "action": {
  206. "type": "block"
  207. }
  208. }
  209. ]
  210. """#
  211. WKContentRuleListStore.default().compileContentRuleList(forIdentifier: "AdBlocker", encodedContentRuleList: contentRules) { ruleList, error in
  212. if let ruleList = ruleList {
  213. config.userContentController.add(ruleList)
  214. } else {
  215. print("Failed to compile content rule list: \(error?.localizedDescription ?? "Unknown error")")
  216. }
  217. completion()
  218. }
  219. }
  220. @objc func goAction() {
  221. if let text = textField.text, !text.isEmpty {
  222. var urlString = text
  223. if !text.starts(with: "www.") && !text.starts(with: "https://") {
  224. urlString = "https://www.google.com/search?q=\(text)"
  225. }
  226. urlString = urlString.trimmingCharacters(in: .whitespacesAndNewlines).replacingOccurrences(of: " ", with: "+")
  227. if let url = URL(string: urlString) {
  228. loadURLWithCookie(url: url)
  229. }
  230. }
  231. }
  232. func loadURLWithCookie(url: URL) {
  233. var urlRequest = URLRequest(url: url)
  234. 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())"
  235. urlRequest.setValue(customUserAgent, forHTTPHeaderField: "User-Agent")
  236. if let cookies = HTTPCookieStorage.shared.cookies(for: url) {
  237. for cookie in cookies {
  238. webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
  239. }
  240. textField.text = url.absoluteString
  241. webView.load(urlRequest)
  242. }
  243. }
  244. public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
  245. return true
  246. }
  247. public func scrollViewDidScroll(_ scrollView: UIScrollView) {
  248. }
  249. public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
  250. if message.name == "sendQueueBNI" {
  251. guard let dict = message.body as? [String: AnyObject],
  252. let param1 = dict["param1"] as? String else {
  253. return
  254. }
  255. DispatchQueue.global().async {
  256. let _ = Nexilis.writeSync(message: CoreMessage_TMessageBank.queueBNI(service_id: param1), timeout: 30 * 1000)
  257. }
  258. }
  259. if message.name == "checkProfile" {
  260. guard let dict = message.body as? [String: AnyObject],
  261. let param1 = dict["param1"] as? String,
  262. let param2 = dict["param2"] as? String else {
  263. return
  264. }
  265. if Nexilis.checkIsChangePerson() {
  266. if param2 == "like" {
  267. self.webView.evaluateJavaScript("likeProduct('\(param1)',1,true);")
  268. } else if param2 == "comment" {
  269. self.webView.evaluateJavaScript("openComment('\(param1.split(separator: "|")[0])',\(param1.split(separator: "|")[1]),true);")
  270. } else if param2 == "report_user" {
  271. self.webView.evaluateJavaScript("reportUser('\(param1)',true);")
  272. } else if param2 == "report_content" {
  273. self.webView.evaluateJavaScript("reportContent('\(param1.split(separator: "|")[0])','\(param1.split(separator: "|")[1])',true);")
  274. } else if param2 == "block_user" {
  275. self.webView.evaluateJavaScript("blockUser('\(param1)',true);")
  276. } else if param2 == "follow_user" {
  277. self.webView.evaluateJavaScript("followUser('\(param1.split(separator: "|")[0])',\(param1.split(separator: "|")[1]),true);")
  278. } else if param2 == "homepage" || param2 == "gif" {
  279. self.webView.evaluateJavaScript("window.location.href = '\(param1)';")
  280. } else if param2 == "block_content" {
  281. self.webView.evaluateJavaScript("blockContent('\(param1)',true);")
  282. } else {
  283. self.webView.evaluateJavaScript("openNewPost(true);")
  284. }
  285. } else {
  286. self.webView.evaluateJavaScript("{if(pauseAll){pauseAll();}}")
  287. }
  288. } else if message.name == "setIsProductModalOpen" {
  289. guard let dict = message.body as? [String: AnyObject],
  290. let param1 = dict["param1"] as? Bool else {
  291. return
  292. }
  293. if param1 {
  294. if self.webView.scrollView.contentOffset.y < 0 { // Move tableView to top
  295. self.webView.scrollView.setContentOffset(CGPoint.zero, animated: true)
  296. }
  297. }
  298. } else if message.name == "toggleVoiceSearch" {
  299. if !isAllowSpeech {
  300. setupSpeech()
  301. } else {
  302. runVoice()
  303. }
  304. } else if message.name == "blockUser" {
  305. guard let dict = message.body as? [String: AnyObject],
  306. let param1 = dict["param1"] as? String,
  307. let param2 = dict["param2"] as? Bool else {
  308. return
  309. }
  310. if param2 {
  311. DispatchQueue.global().async {
  312. if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getBlock(l_pin: param1)) {
  313. if response.isOk() {
  314. DispatchQueue.main.async {
  315. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  316. do {
  317. _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [
  318. "ex_block" : "1"
  319. ], _where: "f_pin = '\(param1)'")
  320. } catch {
  321. rollback.pointee = true
  322. print("Access database error: \(error.localizedDescription)")
  323. }
  324. })
  325. }
  326. }
  327. }
  328. }
  329. } else {
  330. DispatchQueue.global().async {
  331. if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getUnBlock(l_pin: param1)) {
  332. if response.isOk() {
  333. DispatchQueue.main.async {
  334. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  335. do {
  336. _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [
  337. "ex_block" : "0"
  338. ], _where: "f_pin = '\(param1)'")
  339. } catch {
  340. rollback.pointee = true
  341. print("Access database error: \(error.localizedDescription)")
  342. }
  343. })
  344. }
  345. }
  346. }
  347. }
  348. }
  349. } else if message.name == "showAlert" {
  350. guard let dict = message.body as? [String: AnyObject],
  351. let param1 = dict["param1"] as? String else {
  352. return
  353. }
  354. self.view.makeToast(param1, duration: 3)
  355. } else if message.name == "blockUser" {
  356. guard let dict = message.body as? [String: AnyObject],
  357. let param1 = dict["param1"] as? String,
  358. let param2 = dict["param2"] as? Bool else {
  359. return
  360. }
  361. if param2 {
  362. DispatchQueue.global().async {
  363. if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getBlock(l_pin: param1)) {
  364. if response.isOk() {
  365. DispatchQueue.main.async {
  366. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  367. do {
  368. _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [
  369. "ex_block" : "1"
  370. ], _where: "f_pin = '\(param1)'")
  371. } catch {
  372. rollback.pointee = true
  373. print("Access database error: \(error.localizedDescription)")
  374. }
  375. })
  376. }
  377. }
  378. }
  379. }
  380. } else {
  381. DispatchQueue.global().async {
  382. if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getUnBlock(l_pin: param1)) {
  383. if response.isOk() {
  384. DispatchQueue.main.async {
  385. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  386. do {
  387. _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [
  388. "ex_block" : "0"
  389. ], _where: "f_pin = '\(param1)'")
  390. } catch {
  391. rollback.pointee = true
  392. print("Access database error: \(error.localizedDescription)")
  393. }
  394. })
  395. }
  396. }
  397. }
  398. }
  399. }
  400. } else if message.name == "successChangeTheme" {
  401. guard let dict = message.body as? [String: AnyObject],
  402. let param1 = dict["param1"] as? String,
  403. let param2 = dict["param2"] as? Bool else {
  404. return
  405. }
  406. Utils.setMyTheme(value: param1)
  407. Utils.setIsLoadThemeFromOther(value: true)
  408. Utils.resetValueSuperApp()
  409. Utils.setLastTabSelected(value: 0)
  410. if let jsonArray = try! JSONSerialization.jsonObject(with: param1.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [AnyObject] {
  411. do {
  412. for json in jsonArray {
  413. if json["KEY"] as! String == "app_builder_url_webview_1" {
  414. Utils.setURLFirstTab(value: json["VALUE"] as! String)
  415. }
  416. if json["KEY"] as! String == "app_builder_url_webview_2" {
  417. Utils.setURLThirdTab(value: json["VALUE"] as! String)
  418. }
  419. if json["KEY"] as! String == "app_builder_url_webview_3" {
  420. Utils.setURLWv3(value: json["VALUE"] as! String)
  421. }
  422. if json["KEY"] as! String == "app_builder_url_webview_4" {
  423. Utils.setURLWv4(value: json["VALUE"] as! String)
  424. }
  425. if json["KEY"] as! String == "app_builder_url_webview_5" {
  426. Utils.setURLWv5(value: json["VALUE"] as! String)
  427. }
  428. if json["KEY"] as! String == "app_builder_url_webview_6" {
  429. Utils.setURLWv6(value: json["VALUE"] as! String)
  430. }
  431. if json["KEY"] as! String == "app_builder_url_status_update" {
  432. Utils.setURLStatusUpdate(value: json["VALUE"] as! String)
  433. }
  434. if json["KEY"] as! String == "app_builder_custom_tab" {
  435. Utils.setCustomTab(cust: json["VALUE"] as! String)
  436. }
  437. if json["KEY"] as! String == "app_builder_icon_dock" {
  438. Utils.setIconDock(value: json["VALUE"] as! String)
  439. }
  440. if json["KEY"] as! String == "app_builder_background" {
  441. Utils.setBackground(value: json["VALUE"] as! String)
  442. }
  443. if json["KEY"] as! String == "app_builder_background_light" {
  444. Utils.setBackgroundLight(value: json["VALUE"] as! String)
  445. }
  446. if json["KEY"] as! String == "app_builder_background_dark" {
  447. Utils.setBackgroundDark(value: json["VALUE"] as! String)
  448. }
  449. if json["KEY"] as! String == "app_builder_background_1" {
  450. Utils.setBackgroundTab1(value: json["VALUE"] as! String)
  451. }
  452. if json["KEY"] as! String == "app_builder_background_2" {
  453. Utils.setBackgroundTab2(value: json["VALUE"] as! String)
  454. }
  455. if json["KEY"] as! String == "app_builder_background_3" {
  456. Utils.setBackgroundTab3(value: json["VALUE"] as! String)
  457. }
  458. if json["KEY"] as! String == "app_builder_background_4" {
  459. Utils.setBackgroundTab4(value: json["VALUE"] as! String)
  460. }
  461. if json["KEY"] as! String == "app_builder_background_5" {
  462. Utils.setBackgroundTab5(value: json["VALUE"] as! String)
  463. }
  464. if json["KEY"] as! String == "app_builder_background_6" {
  465. Utils.setBackgroundTab6(value: json["VALUE"] as! String)
  466. }
  467. if json["KEY"] as! String == "access_model" {
  468. Utils.setCpaasMode(mode: Int(json["VALUE"] as! String) ?? 0)
  469. }
  470. if json["KEY"] as! String == "app_builder_custom_buttons" {
  471. Utils.setCustomButtons(value: json["VALUE"] as! String)
  472. }
  473. if json["KEY"] as! String == "cpaas_icon" {
  474. Utils.setIconDock(value: json["VALUE"] as! String)
  475. }
  476. if json["KEY"] as! String == "tab1_icon" {
  477. Utils.setTab1Icon(value: json["VALUE"] as! String)
  478. }
  479. if json["KEY"] as! String == "tab2_icon" {
  480. Utils.setTab2Icon(value: json["VALUE"] as! String)
  481. }
  482. if json["KEY"] as! String == "tab3_icon" {
  483. Utils.setTab3Icon(value: json["VALUE"] as! String)
  484. }
  485. if json["KEY"] as! String == "tab4_icon" {
  486. Utils.setTab4Icon(value: json["VALUE"] as! String)
  487. }
  488. if json["KEY"] as! String == "tab5_icon" {
  489. Utils.setTab5Icon(value: json["VALUE"] as! String)
  490. }
  491. if json["KEY"] as! String == "tab6_icon" {
  492. Utils.setTab6Icon(value: json["VALUE"] as! String)
  493. }
  494. if json["KEY"] as! String == "app_builder_button_icon" {
  495. Utils.setButtonIcon(value: json["VALUE"] as! String)
  496. }
  497. if json["KEY"] as! String == "reverse_tab_color" {
  498. Utils.setReverseTab(value: json["VALUE"] as! String)
  499. }
  500. if json["KEY"] as! String == "icon_size" {
  501. Utils.setIconDockSize(value: json["VALUE"] as! String)
  502. }
  503. if json["KEY"] as! String == "app_id" {
  504. changeIconApp(appId: json["VALUE"] as! String)
  505. }
  506. }
  507. } catch {
  508. }
  509. }
  510. Database.shared.database?.inTransaction({ fmdb, rollback in
  511. do {
  512. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ", _where: "")
  513. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ_MEMBER", _where: "")
  514. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "DISCUSSION_FORUM", _where: "")
  515. _ = Nexilis.write(message: CoreMessage_TMessageBank.getPostRegistration(p_pin: User.getMyPin() ?? ""))
  516. } catch {
  517. rollback.pointee = true
  518. print("Access database error: \(error.localizedDescription)")
  519. }
  520. })
  521. let alert = LibAlertController(title: "Successfully changed".localized(), message: "Please open the app again to see the changes".localized(), preferredStyle: .alert)
  522. alert.addAction(UIAlertAction(title: "OK".localized(), style: .default, handler: {(_) in
  523. exit(0)
  524. }))
  525. self.present(alert, animated: true, completion: nil)
  526. } else if message.name == "finishForm" {
  527. if self.webView.canGoBack {
  528. self.webView.goBack()
  529. } else {
  530. self.dismiss(animated: true)
  531. }
  532. } else if message.name == "shareText" {
  533. guard let dict = message.body as? [String: AnyObject],
  534. let param1 = dict["param1"] as? String else {
  535. return
  536. }
  537. if loadingURL {
  538. return
  539. }
  540. let activityViewController = UIActivityViewController(activityItems: [param1], applicationActivities: nil)
  541. self.present(activityViewController, animated: true, completion: nil)
  542. } else if message.name == "openGalleryiOS" {
  543. guard let dict = message.body as? [String: AnyObject],
  544. let param1 = dict["param1"] as? Int else {
  545. return
  546. }
  547. indexImageVideoWv = param1
  548. let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  549. if let action = self.actionImageVideo(for: "image", title: "Choose Photo".localized()) {
  550. alertController.addAction(action)
  551. }
  552. if let action = self.actionImageVideo(for: "video", title: "Choose Video".localized()) {
  553. alertController.addAction(action)
  554. }
  555. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
  556. self.present(alertController, animated: true)
  557. }
  558. }
  559. private func actionImageVideo(for type: String, title: String) -> UIAlertAction? {
  560. return UIAlertAction(title: title, style: .default) { [unowned self] _ in
  561. switch type {
  562. case "image":
  563. imageVideoPicker.present(source: .imageAlbum)
  564. case "video":
  565. imageVideoPicker.present(source: .videoAlbum)
  566. default:
  567. imageVideoPicker.present(source: .imageAlbum)
  568. }
  569. }
  570. }
  571. public func didSelect(imagevideo: Any?) {
  572. if imagevideo != nil {
  573. let imageData = imagevideo! as! [UIImagePickerController.InfoKey : Any]
  574. if (imageData[.mediaType] as! String == "public.image") {
  575. let compressedImage = (imageData[.originalImage] as! UIImage).pngData()!
  576. let base64String = compressedImage.base64EncodedString()
  577. let base64ToWeb = "data:image/jpeg;base64,\(base64String)"
  578. webView.evaluateJavaScript("loadFromMobile('\(base64ToWeb)',\(indexImageVideoWv))") { (result, error) in
  579. if let error = error {
  580. print("Error executing JavaScript: \(error)")
  581. }
  582. }
  583. } else {
  584. guard var dataVideo = try? Data(contentsOf: imageData[.mediaURL] as! URL) else {
  585. return
  586. }
  587. let sizeOfVideo = Double(dataVideo.count / 1048576)
  588. if (sizeOfVideo > 10.0) {
  589. let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4")
  590. compressVideo(inputURL: imageData[.mediaURL] as! URL,
  591. outputURL: compressedURL) { exportSession in
  592. guard let session = exportSession else {
  593. return
  594. }
  595. switch session.status {
  596. case .unknown:
  597. break
  598. case .waiting:
  599. break
  600. case .exporting:
  601. break
  602. case .completed:
  603. guard let compressedData = try? Data(contentsOf: compressedURL) else {
  604. return
  605. }
  606. dataVideo = compressedData
  607. case .failed:
  608. break
  609. case .cancelled:
  610. break
  611. @unknown default:
  612. break
  613. }
  614. }
  615. }
  616. let base64String = dataVideo.base64EncodedString()
  617. let base64ToWeb = "data:video/mp4;base64,\(base64String)"
  618. webView.evaluateJavaScript("loadFromMobile('\(base64ToWeb)',\(indexImageVideoWv))") { (result, error) in
  619. if let error = error {
  620. print("Error executing JavaScript: \(error)")
  621. }
  622. }
  623. }
  624. }
  625. }
  626. func compressVideo(inputURL: URL,
  627. outputURL: URL,
  628. handler:@escaping (_ exportSession: AVAssetExportSession?) -> Void) {
  629. let urlAsset = AVURLAsset(url: inputURL, options: nil)
  630. guard let exportSession = AVAssetExportSession(asset: urlAsset,
  631. presetName: AVAssetExportPresetMediumQuality) else {
  632. handler(nil)
  633. return
  634. }
  635. exportSession.outputURL = outputURL
  636. exportSession.outputFileType = .mp4
  637. exportSession.exportAsynchronously {
  638. handler(exportSession)
  639. }
  640. }
  641. func changeIconApp(appId: String) {
  642. let digisalesKey = "1694457466830"
  643. let nuKey = "1693550500075"
  644. let iknKey = "1693542580518"
  645. let diginetsKey = "1693456149709"
  646. let biKey = "1692873053159"
  647. let nxcookKey = "1692863737543"
  648. let nxsportKey = "1692863037019"
  649. let bpkhKey = "1711023277251"
  650. let disiniKey = "1711024221024"
  651. let gudegKey = "1712052403416"
  652. let kmiKey = "1713407687550"
  653. let waKey = "1744166263877"
  654. if appId == waKey {
  655. Utils.setIsWATheme(value: true)
  656. } else {
  657. Utils.setIsWATheme(value: false)
  658. }
  659. switch appId {
  660. case digisalesKey:
  661. UIApplication.shared.setAlternateIconName("digisales_icon")
  662. case nuKey:
  663. UIApplication.shared.setAlternateIconName("nu_icon")
  664. case iknKey:
  665. UIApplication.shared.setAlternateIconName("ikn_icon")
  666. case diginetsKey:
  667. UIApplication.shared.setAlternateIconName("diginets_icon")
  668. case biKey:
  669. UIApplication.shared.setAlternateIconName("bi_icon")
  670. case nxcookKey:
  671. UIApplication.shared.setAlternateIconName("nxcook_icon")
  672. case nxsportKey:
  673. UIApplication.shared.setAlternateIconName("nxsport_icon")
  674. case bpkhKey:
  675. UIApplication.shared.setAlternateIconName("bpkh_icon")
  676. case disiniKey:
  677. UIApplication.shared.setAlternateIconName("disini_icon")
  678. case gudegKey:
  679. UIApplication.shared.setAlternateIconName("gudeg_icon")
  680. case kmiKey:
  681. UIApplication.shared.setAlternateIconName("kmi_icon")
  682. default:
  683. UIApplication.shared.setAlternateIconName(nil)
  684. }
  685. }
  686. func setupSpeech() {
  687. self.speechRecognizer?.delegate = self
  688. SFSpeechRecognizer.requestAuthorization { (authStatus) in
  689. var isButtonEnabled = false
  690. switch authStatus {
  691. case .authorized:
  692. isButtonEnabled = true
  693. case .denied:
  694. isButtonEnabled = false
  695. //print("User denied access to speech recognition")
  696. case .restricted:
  697. isButtonEnabled = false
  698. //print("Speech recognition restricted on this device")
  699. case .notDetermined:
  700. isButtonEnabled = false
  701. //print("Speech recognition not yet authorized")
  702. @unknown default:
  703. isButtonEnabled = false
  704. }
  705. OperationQueue.main.addOperation() {
  706. self.isAllowSpeech = isButtonEnabled
  707. if isButtonEnabled {
  708. SecureUserDefaults.shared.set(isButtonEnabled, forKey: "allowSpeech")
  709. self.runVoice()
  710. }
  711. }
  712. }
  713. }
  714. func runVoice() {
  715. if !audioEngine.isRunning {
  716. alertController = LibAlertController(title: "Start Recording".localized(), message: "Say something, I'm listening!".localized(), preferredStyle: .alert)
  717. self.present(alertController, animated: true)
  718. self.webView.evaluateJavaScript("toggleVoiceButton(true)")
  719. self.startRecording()
  720. }
  721. }
  722. func startRecording() {
  723. // Clear all previous session data and cancel task
  724. if recognitionTask != nil {
  725. recognitionTask?.cancel()
  726. recognitionTask = nil
  727. }
  728. // Create instance of audio session to record voice
  729. let audioSession = AVAudioSession.sharedInstance()
  730. do {
  731. try audioSession.setCategory(AVAudioSession.Category.record, mode: .default, options: [])
  732. try audioSession.setMode(AVAudioSession.Mode.measurement)
  733. try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
  734. } catch {
  735. //print("audioSession properties weren't set because of an error.")
  736. }
  737. self.recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
  738. let inputNode = audioEngine.inputNode
  739. guard let recognitionRequest = recognitionRequest else {
  740. fatalError("Unable to create an SFSpeechAudioBufferRecognitionRequest object")
  741. }
  742. recognitionRequest.shouldReportPartialResults = true
  743. self.recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest, resultHandler: { (result, error) in
  744. var isFinal = false
  745. var text = ""
  746. if result != nil {
  747. text = result?.bestTranscription.formattedString ?? ""
  748. isFinal = (result?.isFinal)!
  749. self.alertController.dismiss(animated: true)
  750. self.audioEngine.stop()
  751. self.recognitionRequest?.endAudio()
  752. } else {
  753. self.alertController.dismiss(animated: true)
  754. }
  755. if error != nil || isFinal {
  756. if error == nil {
  757. self.webView.evaluateJavaScript("toggleVoiceButton(false)")
  758. self.webView.evaluateJavaScript("submitVoiceSearch('\(text)')")
  759. } else {
  760. self.audioEngine.stop()
  761. self.recognitionRequest?.endAudio()
  762. }
  763. inputNode.removeTap(onBus: 0)
  764. self.recognitionRequest = nil
  765. self.recognitionTask = nil
  766. self.isAllowSpeech = true
  767. }
  768. })
  769. let recordingFormat = inputNode.outputFormat(forBus: 0)
  770. inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer, when) in
  771. self.recognitionRequest?.append(buffer)
  772. }
  773. self.audioEngine.prepare()
  774. do {
  775. try self.audioEngine.start()
  776. } catch {
  777. //print("audioEngine couldn't start because of an error.")
  778. }
  779. }
  780. @objc func reloadWebView(_ sender: UIRefreshControl) {
  781. webView.reload()
  782. sender.endRefreshing()
  783. }
  784. public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) {
  785. guard let url = navigationAction.request.url else {
  786. // print("return 0")
  787. decisionHandler(.cancel)
  788. return
  789. }
  790. if let scheme = url.scheme?.lowercased(), scheme.hasPrefix("itms") || scheme == "mailto" || scheme == "tel" {
  791. DispatchQueue.main.async {
  792. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  793. }
  794. // print("return 1 \(url.absoluteString)")
  795. decisionHandler(.cancel)
  796. return
  797. }
  798. guard navigationAction.targetFrame?.isMainFrame == true else {
  799. decisionHandler(.allow)
  800. // print("return 2 \(url.absoluteString)")
  801. return
  802. }
  803. if loadingURL {
  804. decisionHandler(.cancel)
  805. // print("return 3 \(url.absoluteString)")
  806. return
  807. }
  808. loadingURL = true
  809. if allowedURLs.contains(url.absoluteString) {
  810. loadingURL = false
  811. decisionHandler(.allow)
  812. // print("return 4 \(url.absoluteString)")
  813. return
  814. }
  815. validateSSLCertificate(url: url) { [weak self] isValid in
  816. guard let self = self else {
  817. // print("return 5 \(url.absoluteString)")
  818. decisionHandler(.cancel)
  819. return
  820. }
  821. DispatchQueue.main.async {
  822. if isValid {
  823. self.allowedURLs.insert(url.absoluteString)
  824. self.loadingURL = false
  825. // print("return 6 \(url.absoluteString)")
  826. decisionHandler(.allow)
  827. } else {
  828. let host = url.host ?? ""
  829. 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()
  830. messageText = messageText.replacingOccurrences(of: "<<domain>>", with: host)
  831. let alert = UIAlertController(title: "Warning Unknown Url!".localized(),
  832. message: messageText,
  833. preferredStyle: .alert)
  834. alert.addAction(UIAlertAction(title: "Yes", style: .default) { _ in
  835. let storedCertificate = Utils.getCertificatePinningWebview()
  836. if let jsonData = storedCertificate.data(using: .utf8),
  837. let certJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
  838. var certJson = certJson
  839. certJson[host] = self.blockedCertificate
  840. if let jsonData = try? JSONSerialization.data(withJSONObject: certJson, options: []),
  841. let jsonString = String(data: jsonData, encoding: .utf8) {
  842. Utils.setCertificatePinningWebview(value: jsonString)
  843. }
  844. }
  845. self.allowedURLs.insert(url.absoluteString)
  846. self.loadingURL = false
  847. decisionHandler(.allow)
  848. })
  849. alert.addAction(UIAlertAction(title: "No", style: .cancel) { _ in
  850. self.loadingURL = false
  851. decisionHandler(.cancel)
  852. })
  853. if self.presentedViewController == nil {
  854. self.present(alert, animated: true, completion: nil)
  855. } else {
  856. self.loadingURL = false
  857. // print("return 7 \(url.absoluteString)")
  858. decisionHandler(.cancel)
  859. }
  860. }
  861. }
  862. }
  863. }
  864. private func validateSSLCertificate(url: URL, completion: @escaping (Bool) -> Void) {
  865. let session = URLSession(configuration: .ephemeral, delegate: self, delegateQueue: nil)
  866. let request = URLRequest(url: url)
  867. let task = session.dataTask(with: request) { _, response, error in
  868. if let error = error {
  869. completion(false)
  870. return
  871. }
  872. completion(true)
  873. }
  874. task.resume()
  875. }
  876. }
  877. extension BNIBookingWebView: URLSessionDelegate {
  878. public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
  879. guard let serverTrust = challenge.protectionSpace.serverTrust else {
  880. completionHandler(.cancelAuthenticationChallenge, nil)
  881. return
  882. }
  883. if let publicKeyHash = extractPublicKeyHash(from: serverTrust) {
  884. let domain = challenge.protectionSpace.host
  885. let storedCertificate = Utils.getCertificatePinningWebview()
  886. if let jsonData = storedCertificate.data(using: .utf8),
  887. let certJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
  888. if publicKeyHash == certJson[domain] {
  889. completionHandler(.useCredential, URLCredential(trust: serverTrust))
  890. } else {
  891. blockedCertificate = publicKeyHash
  892. completionHandler(.cancelAuthenticationChallenge, nil)
  893. }
  894. }
  895. } else {
  896. completionHandler(.cancelAuthenticationChallenge, nil)
  897. }
  898. }
  899. func extractPublicKeyHash(from serverTrust: SecTrust) -> String? {
  900. guard let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else { return nil }
  901. guard let publicKey = SecCertificateCopyKey(certificate) else { return nil }
  902. var error: Unmanaged<CFError>?
  903. guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
  904. return nil
  905. }
  906. // Compute SHA-256 hash
  907. var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
  908. publicKeyData.withUnsafeBytes {
  909. _ = CC_SHA256($0.baseAddress, CC_LONG(publicKeyData.count), &hash)
  910. }
  911. let hashData = Data(hash)
  912. let base64Hash = hashData.base64EncodedString()
  913. return base64Hash
  914. }
  915. }