Class controller Webview ```swift class VisualizacionController : UIViewController, WKUIDelegate, WKNavigationDelegate, UIWebViewDelegate, UIPopoverPresentationControllerDelegate, WKScriptMessageHandler{ @IBOutlet weak var viewImage: UIView! @IBOutlet weak var viewWebView: UIView! @IBOutlet weak var viewError: UIView! @IBOutlet weak var etError: UILabel! var wkWebView: WKWebView! var loadingView : LoadingView! private var bConexionOK: Bool? private var textoError: String = "" private var m_SSLtargetURL: String = "" private var paramsIn: String? private var entidadIn: Int? private var servicioIn: Int? private var servicio: Servicio? private var paramsEntrada: [String: String]? private var datosDnie: DatosDNIe? var clave: UsuarioClaveDO? private var _htmlBody: String? = "" private var _formsBuscar: [String]? = nil private var _inputsBuscar: [String]? = nil private var _buscaExiste: Bool? = nil private var postParamenters: String? = nil private var lForms: [String] = [String]() private var isGet = false private var isPost = false private var _ficheroDescarga: FicheroDescarga? = nil private var _mimetype: String? = nil func muestraCarga(texto: String) { if self.loadingView == nil || !self.loadingView.showing { DispatchQueue.main.async { self.loadingView = LoadingView(message: texto) self.present(self.loadingView.alert!, animated: true, completion: { self.loadingView.showing = true }) } } } func ocultaCarga() { if(self.loadingView != nil) { self.loadingView.stop() //Esperamos mientras no cierra el loadingview DispatchQueue.global(qos: .background).async { while (self.loadingView.showing) { sleep(1) self.loadingView.stop() } } } } func actualizaCarga(texto: String) { if self.loadingView != nil { self.loadingView.updateMessage(message: texto) } } override func viewDidLoad() { super.viewDidLoad() self.navigationController?.navigationBar.tintColor = ColorUtils.uicolorFromHex(rgbValue: 0xffffff) self.navigationController?.navigationBar.barTintColor = ColorUtils.uicolorFromHex(rgbValue: 0x39759f) self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white] //Recuperamos los datos almacenados en la app clave = Tramite.selectedClave datosDnie = Tramite.datosDnie // Recuperamos los parámetros que hemos recopilado sobre el servicio al que queríamos conectar m_SSLtargetURL = Tramite.m_url paramsIn = Tramite.paramas entidadIn = Tramite.entidad servicioIn = Tramite.servicio paramsEntrada = Tramite.parametros viewImage.isHidden = false viewWebView.isHidden = true viewError.isHidden = true bConexionOK = true textoError = "" //Crear webview let preferences = WKPreferences() preferences.javaScriptEnabled = true let config = WKWebViewConfiguration() let js = getMyJavaScript() let script = WKUserScript(source: js, injectionTime: .atDocumentEnd, forMainFrameOnly: false) config.preferences = preferences config.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs") config.websiteDataStore = WKWebsiteDataStore.nonPersistent() config.userContentController.addUserScript(script) config.userContentController.add(self, name: "submit") config.userContentController.add(self, name: "recuperaFormularioEnviado") config.userContentController.add(self, name: "recuperaTodosFormularios") config.userContentController.add(self, name: "getBase64FromBlobData") wkWebView = WKWebView(frame: viewWebView.bounds, configuration: config) wkWebView.autoresizingMask = [.flexibleWidth, .flexibleHeight] self.viewWebView.addSubview(wkWebView) wkWebView.allowsBackForwardNavigationGestures = true wkWebView.uiDelegate = self wkWebView.navigationDelegate = self DispatchQueue.global(qos: .background).async { do { self.muestraCarga(texto: "Conectando...") try self.procesarAsincrono() self.bConexionOK = true }catch (TramiteException.CancelledOperationException(let txtError)){ self.textoError = txtError self.bConexionOK=false }catch (TramiteException.ServiceDataException(let txtError)){ self.textoError = txtError self.bConexionOK=false let tramite: ServicioEnum = ServicioEnum.getByCodigo(codigo: self.servicioIn!)! let entidad: EntidadEnum = EntidadEnum.getByCodigo(codigo: self.entidadIn!)! let tipoIdentificacion: TipoIdentificacion = ConfiguracionUtils.obtenerTipoIdentificacion() FirebaseUtils.registraErrorTramite(tramite: tramite.getDescripcion(), administracion: entidad.getDescripcion(), validacion: tipoIdentificacion.getDescripcion(), error: self.textoError) }catch{ self.textoError = error.localizedDescription self.bConexionOK=false let tramite: ServicioEnum = ServicioEnum.getByCodigo(codigo: self.servicioIn!)! let entidad: EntidadEnum = EntidadEnum.getByCodigo(codigo: self.entidadIn!)! let tipoIdentificacion: TipoIdentificacion = ConfiguracionUtils.obtenerTipoIdentificacion() var stramite: String = "" if(tramite != nil) { stramite=tramite.getDescripcion() } var sentidad: String = "" if(entidad != nil) { sentidad=entidad.getDescripcion() } var stipoIdentificacion: String = "" if(tipoIdentificacion != nil) { stipoIdentificacion=tipoIdentificacion.getDescripcion() } FirebaseUtils.registraErrorTramite(tramite: stramite, administracion: sentidad, validacion: stipoIdentificacion, error: self.textoError) } DispatchQueue.main.async { if(self.bConexionOK!) { self.cargaContentido() } else { self.HandleError(error: self.textoError) } } } } func procesarAsincrono() throws{ let despachador = Despachador() servicio = try despachador.depachaPeticiones(entidadIn: entidadIn!, servicioIn: servicioIn!, datosDnie: datosDnie!, viewController: self) Tramite.servicioUtilizar = servicio m_SSLtargetURL = servicio!.parseaParametros(&m_SSLtargetURL, paramsIn: paramsIn ?? "", paramsEntrada: paramsEntrada) // Actualizamos el estado del progressDialog actualizaCarga(texto: "Conectando...") _htmlBody = try servicio!.procesaUrl(m_SSLtragetURL: m_SSLtargetURL) } func cargaContentido(){ if !(servicio!.isAbrepdf()!) { //Si no estamos abriendo un pdf es que estamos procesando una url if (_htmlBody != nil) { // Adaptamos el contenido web a las style sheet locales _htmlBody = servicio!.optimizaWeb(contenido: _htmlBody!) loadHtmlCode(htmlCode: _htmlBody!, finalurl: Tramite.finalUrl, baseURL: (servicio?.getBaseURL())!) } else { //Volvemos a la activity de inicio si no es un documento y el html es null self.dismiss(animated: false, completion: nil) } } else { loadHtmlCode(htmlCode: "", finalurl: servicio?.getRutapdf(), baseURL: "") } } public func HandleError(error: String) { ocultaCarga() etError.text = error viewError.isHidden = false viewImage.isHidden = true viewWebView.isHidden = true } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func loadHtmlCode(htmlCode: String, finalurl: String?, baseURL: String) { actualizaCarga(texto: "Cargando contenido. Por favor, espere...") // let dataStore = WKWebsiteDataStore.default() // dataStore.fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in // dataStore.removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), // for: records, // completionHandler: {}) // } //Volvemos a asignar las cookies al webView let cookies = HTTPCookieStorage.shared.cookies ?? [] for (cookie) in cookies { wkWebView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie) } if (finalurl != nil) { let url = URL(string: finalurl!) let requestObj = URLRequest(url: url! as URL) wkWebView.load(requestObj) } else { var base: URL? = nil if (!baseURL.isEmpty) { base = URL(string: baseURL) } //Mostramos el html procesado wkWebView.loadHTMLString(htmlCode, baseURL: base) } } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { ocultaCarga() viewImage.isHidden = true viewError.isHidden = true viewWebView.isHidden = false postParamenters = nil lForms.removeAll() } //Solicitar validación con certificado func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { let method = challenge.protectionSpace.authenticationMethod switch method { case NSURLAuthenticationMethodClientCertificate: sendClientCertificate(for: challenge, via: completionHandler) default: completionHandler(.performDefaultHandling, .none) } } func sendClientCertificate(for challenge: URLAuthenticationChallenge, via completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { let certificado: String? = Tramite.selectedCertificado let password: String? = Tramite.passwordCertificado if(certificado != nil && password != nil ) { guard let data = try? Data(contentsOf: URL(fileURLWithPath: certificado!)), let credential = credential(from: data, withPassword: password ?? "") else { challenge.sender?.cancel(challenge) return completionHandler(.rejectProtectionSpace, .none) } return completionHandler(.useCredential, credential); } else{ return completionHandler(.rejectProtectionSpace, .none) } } func credential(from data: Data, withPassword password: String) -> URLCredential? { guard let security = security(from: data, withPassword: password) else { return .none } return URLCredential( identity: security.identity, certificates: security.certificates, persistence: .permanent ) } func security(from data: Data, withPassword password: String) -> (identity: SecIdentity, trust: SecTrust, certificates: [SecCertificate])? { var _items: CFArray? let securityError = SecPKCS12Import(data as NSData, [ kSecImportExportPassphrase as String : password ] as CFDictionary, &_items); guard let items = _items as? [Any], let dict = items.first as? [String:Any], securityError == errSecSuccess else { return .none } let identity = dict["identity"] as! SecIdentity let trust = dict["trust"] as! SecTrust; // Certificate chain var certificate: SecCertificate! SecIdentityCopyCertificate(identity, &certificate); return (identity, trust, [certificate]); } override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { if #available(iOS 13, *), viewControllerToPresent is UIDocumentPickerViewController { viewControllerToPresent.popoverPresentationController?.delegate = self } super.present(viewControllerToPresent, animated: flag, completion: completion) } func prepareForPopoverPresentation(_ popoverPresentationController: UIPopoverPresentationController) { popoverPresentationController.sourceView = self.view } //Descarga documentos func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { let request = navigationAction.request var url: String = request.url?.absoluteString ?? "" if(url.startsWith(cadena: "afirma") || url.contains("scheme=afirma")) { let appUrl = request.url! if UIApplication.shared.canOpenURL(appUrl as URL){ UIApplication.shared.open(request.url!) } else { UIUtils.mostrarMensaje(titulo: StringUtils.localizedString(forKey: "titulo_afirma_no_instalado"), mensaje: StringUtils.localizedString(forKey: "afirma_no_instalado"), textoBoton: "Cerrar") } } let mutableRequest = request as! NSMutableURLRequest; if mutableRequest.httpMethod == Constantes.HTTP_METHOD_GET { _ficheroDescarga = nil _mimetype = nil isGet = true isPost = false } else { isGet = false isPost = true } decisionHandler(.allow) } func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { if (servicio != nil && servicio!.isAbrepdf() != nil && !(servicio!.isAbrepdf()!)) //Si no estamos descargando un documento desde un trámite { if let mimeType = navigationResponse.response.mimeType { if !isHtml(mimeType) { if let url = navigationResponse.response.url { let ficheroDescarga = recuperaDatosFichero(navigationResponse.response) _ficheroDescarga = ficheroDescarga _mimetype = mimeType do{ if(url.absoluteString.startsWith(cadena: "blob")) { //No funciona /* var js = JavaScriptUtils.getBase64StringFromBlobUrl(blobUrl: url.absoluteString) webView.evaluateJavaScript(js) { (result, error) in if result != nil { print("Resultado \(result)") } if error != nil { print("Javascript execuption failed: \(error.debugDescription)") } if (result == nil) && (error == nil) { print("Sin resultado") } } */ UIUtils.showToast(view: self.viewWebView, message: "No ha sido posible descargar el documento") decisionHandler(.allow) return } if(!isGet) { //TODO DOWNLOAD POST descargaDocumentoPostData(webView: webView, url: url.absoluteString, postParamenters: postParamenters, lForms: lForms, ficheroDescarga: ficheroDescarga, mimeType: mimeType, servicio: servicio!) postParamenters=nil lForms.removeAll() if (!isPdf(mimeType)) { decisionHandler(.cancel) return } else { decisionHandler(.allow) return } } let httpDownloader: HTTPDownloader = HTTPDownloader() let rutaDocumento = try httpDownloader.downloadFromUrl(url: url.absoluteString, nombre: ficheroDescarga.nombre, descripcion: ficheroDescarga.descripcion, extension: ficheroDescarga.ext, mimeType: mimeType, method: Constantes.METHOD_GET) UIUtils.showToast(view: self.viewWebView, message: "Documento descargado correctamente") } catch { UIUtils.showToast(view: self.viewWebView, message: "No ha sido posible descargar el documento") } if !isPdf(mimeType) { decisionHandler(.cancel) return } } } } } decisionHandler(.allow) } func getMyJavaScript() -> String { if let filepath = Bundle.main.path(forResource: "inject", ofType: "js") { do { return try String(contentsOfFile: filepath) } catch { return "" } } else { return "" } } public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { if (message.name == "submit") { postParamenters = message.body as! String isGet=false isPost=false } else if (message.name == "recuperaFormularioEnviado") { postParamenters = message.body as! String isGet=false isPost=false } else if (message.name == "recuperaTodosFormularios") { let forms = message.body as! String let formularios: [String] = forms.splitea(separador: "###") for formulario in formularios { lForms.append(formulario) } isGet=false isPost=false } else if (message.name == "getBase64FromBlobData") { /* var base64Data = message.body as! String base64Data=base64Data.replacingOccurrences(of: "^data:application/pdf;base64,", with: "") // base64Data=base64Data.replaceFirst("^data:application/pdf;base64,", "") descargaBlobFile(base64Data: base64Data, ficheroDescarga: _ficheroDescarga!, mimeType: _mimetype ?? "", servicio: servicio!) */ } } private func recuperaDatosFichero(_ response:URLResponse) -> FicheroDescarga { var descarga: FicheroDescarga = FicheroDescarga() var nombreFichero = "descarga" var ext = "pdf" var descripcion = "Descarga documento" if let httpResponse = response as? HTTPURLResponse { let headers = httpResponse.allHeaderFields if let disposition = headers["Content-Disposition"] as? String { let filename = "filename=" let pos = disposition.indexOf(target: filename) ?? 0 if(pos>0) { let ficheroConExtension = disposition.substring(from: pos+filename.count) let posPunto = ficheroConExtension.lastIndexOf(target: ".") ?? 0 if (posPunto>0) { nombreFichero = ficheroConExtension.substring(to: posPunto) nombreFichero = nombreFichero.replacingOccurrences(of: "\"", with: "") ext = ficheroConExtension.substring(from: posPunto+1) ext = ext.replacingOccurrences(of: "\"", with: "") } descripcion = disposition.substring(to: pos) descripcion = descripcion.replacingOccurrences(of: "\"", with: "") } } } descarga.nombre = nombreFichero descarga.ext = ext descarga.descripcion = description return descarga } private func isHtml(_ mimeType:String) -> Bool { if mimeType == "text/html" { return true } else { return false } } private func isPdf(_ mimeType:String) -> Bool { if mimeType == "application/pdf" { return true } else { return false } } private func parseaParametros (url: String, servicio: Servicio) -> String{ var urlFinal = url if(url.contains("?")) { let inicioParametros = url.indexOf(target: "?") var parametrosUrl: String = url.substring(from: inicioParametros!+1) urlFinal = url.substring(to: inicioParametros!) if(parametrosUrl.contains("&")) { parametrosUrl = parametrosUrl.replacingOccurrences(of: "&", with: "#amp;") } let params: [String.SubSequence] = parametrosUrl.split(separator: "&") var arr: [String.SubSequence] = params var len = params.count for parametro in arr { let posicion: Int = (String(parametro)).indexOf(target: "=")! if (posicion > 0) { let clave: String = (String(parametro)).substring(to: posicion) var valor: String = (String(parametro)).substring(from: posicion + 1) valor = servicio.encodeParamenters(parametro: valor) valor = valor.replacingOccurrences(of: "%20", with: "+") if(valor.contains("#amp;")) { valor = valor.replacingOccurrences(of: "#amp;", with: "&") } let p = clave + "=" + valor if(urlFinal.contains("?")) { urlFinal += "&" + p } else { urlFinal += "?" + p } } } } return urlFinal } private func descargaDocumentoPostData(webView: WKWebView, url: String, postParamenters: String?, lForms: [String], ficheroDescarga: FicheroDescarga, mimeType: String, servicio: Servicio) { if(postParamenters != nil) { var urlDescarga = url if(!postParamenters!.isEmpty) { if (urlDescarga.contains("?")) { urlDescarga += "&" + postParamenters! } else { urlDescarga += "?" + postParamenters! } } urlDescarga = parseaParametros(url: urlDescarga, servicio: servicio) do{ let httpDownloader: HTTPDownloader = HTTPDownloader() let rutaDocumento = try httpDownloader.downloadFromUrl(url: urlDescarga, nombre: ficheroDescarga.nombre, descripcion: ficheroDescarga.descripcion, extension: ficheroDescarga.ext, mimeType: mimeType, method: Constantes.METHOD_POST) UIUtils.showToast(view: self.viewWebView, message: "Documento descargado correctamente") } catch { UIUtils.showToast(view: self.viewWebView, message: "No ha sido posible descargar el documento") } } else { if(!lForms.isEmpty) { var urlDescarga: String? = nil for formulario in lForms { if(formulario.contains(url)) { urlDescarga=formulario } } if(urlDescarga != nil) { urlDescarga = parseaParametros(url: urlDescarga!, servicio: servicio) do{ let httpDownloader: HTTPDownloader = HTTPDownloader() let rutaDocumento = try httpDownloader.downloadFromUrl(url: urlDescarga!, nombre: ficheroDescarga.nombre, descripcion: ficheroDescarga.descripcion, extension: ficheroDescarga.ext, mimeType: mimeType, method: Constantes.METHOD_POST) UIUtils.showToast(view: self.viewWebView, message: "Documento descargado correctamente") } catch { UIUtils.showToast(view: self.viewWebView, message: "No ha sido posible descargar el documento") } } else { UIUtils.showToast(view: self.viewWebView, message: "No ha sido posible descargar el documento") } } else { UIUtils.showToast(view: self.viewWebView, message: "No ha sido posible descargar el documento") } } } private func descargaBlobFile(base64Data :String, ficheroDescarga: FicheroDescarga, mimeType: String, servicio: Servicio) { do{ try servicio.descargaPDF_B64(documentoBase64: base64Data, nombre: ficheroDescarga.nombre, descripcion: ficheroDescarga.ext, extension: ficheroDescarga.ext, mimeType: mimeType) UIUtils.showToast(view: self.viewWebView, message: "Documento descargado correctamente") } catch { UIUtils.showToast(view: self.viewWebView, message: "No ha sido posible descargar el documento") } } // //Manejador de ventanas que se abren en target blank func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { if navigationAction.targetFrame == nil { webView.load(navigationAction.request) } return nil } } ``` JavascriptUtils ```language public class JavaScriptUtils{ public static func getBase64StringFromBlobUrl(blobUrl: String) -> String { if(blobUrl.startsWith(cadena: "blob")){ var script = "" script = script + "var xhr = new XMLHttpRequest();" script = script + "xhr.open('GET', '\(blobUrl)', true);" script = script + "xhr.setRequestHeader('Content-type','application/pdf');" script = script + "xhr.responseType = 'blob';" script = script + "xhr.onerror = function () { window.webkit.messageHandlers.getBase64FromBlobData.postMessage('Error'); };" script = script + "xhr.onload = function() { window.webkit.messageHandlers.getBase64FromBlobData.postMessage(xhr.status); if (this.status == 200) { var blob = this.response; window.webkit.messageHandlers.getBase64FromBlobData.postMessage(blob); var reader = new window.FileReader(); reader.readAsBinaryString(blob); reader.onloadend = function() { window.webkit.messageHandlers.getBase64FromBlobData.postMessage(reader.result); }}};" script = script + "xhr.send();" return script } return "javascript: console.log('It is not a Blob URL');" } } ```