BNIBookingWebView.swift 43 KB

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