Javascript console log in UIWebView in Swift - javascript

Is there any way to print logs that is created by "console.log" in web page in UIWebView or WKWebView in swift (like Chrome F12.)
Have a nice days.

Here's a basic implementation for the UIWebView:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Create the web view.
let webView = UIWebView()
webView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(webView)
webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
webView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
webView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
webView.delegate = self
webView.loadRequest(URLRequest(url: URL(string: "https://www.google.com.py")!))
}
}
extension ViewController: UIWebViewDelegate {
func webViewDidFinishLoad(_ webView: UIWebView) {
let js = "console.log = function() {window.location = 'logger://webview?' + JSON.stringify(Array.prototype.slice.call(arguments))}"
webView.stringByEvaluatingJavaScript(from: js)
}
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if request.url?.scheme == "logger" {
guard let data = request.url?.query?.removingPercentEncoding?.data(using: .utf8) else { return true }
guard let obj = try? JSONSerialization.jsonObject(with: data, options: []) else { return true }
guard let jsonData = try? JSONSerialization.data(withJSONObject: obj, options: .prettyPrinted) else { return true }
guard let json = String(data: jsonData, encoding: .utf8) else { return true }
print(json)
}
return true
}
}
Example
console.log(4, 3.53, 'Hello', {d: {f: 4}}, function() {}, undefined, null, true)
Prints the following in Xcode log:
[
4,
3.5299999999999998,
"Hello",
{
"d" : {
"f" : 4
}
},
null,
null,
null,
true
]
Caveats
This only prints logs executed after the page has loaded.
Since we're using JSON.stringify, we can't print the following types: function, undefined
Since we're using JSON.stringify, true and false are
printed as 1 and 0, respectively

Related

SwiftUI - How to evaluate javascript in webview if network is down?

I have the following webview implementation built by borrowing code (I am learning SwiftUI). I want to show a message in the webview when the networks is down. It works with a static html loaded from a string and a button but I am unable to do the same calling a web server and triggering javascript using the network status.
import SwiftUI
import WebKit
import Combine
private let urlString: String = "http://127.0.0.1"
class WebViewData: ObservableObject {
#Published var parsedText: NSAttributedString? = nil
var functionCaller = PassthroughSubject<Void,Never>()
var isInit = false
var shouldUpdateView = true
}
struct WebView: UIViewRepresentable {
#StateObject var data: WebViewData
func makeUIView(context: Context) -> WKWebView {
let wkWebview = WKWebView()
wkWebview.navigationDelegate = context.coordinator
return wkWebview
}
func updateUIView(_ uiView: WKWebView, context: Context) {
guard data.shouldUpdateView else {
data.shouldUpdateView = false
return
}
context.coordinator.tieFunctionCaller(data: data)
context.coordinator.webView = uiView
guard let url = URL(string: urlString) else { return }
let request = URLRequest(url: url)
uiView.load(request)
}
func makeCoordinator() -> WebViewCoordinator {
return WebViewCoordinator(view: self)
}
func viewWillAppear(_ uiView: WKWebView, context: UIViewRepresentableContext<WebView>){
}
}
class WebViewCoordinator : NSObject, WKNavigationDelegate {
var parent: WebView
var webView: WKWebView? = nil
private var cancellable : AnyCancellable?
init(view: WebView) {
self.parent = view
super.init()
}
func tieFunctionCaller(data: WebViewData) {
cancellable = data.functionCaller.sink(receiveValue: { _ in
self.webView?.evaluateJavaScript("message_generator(\"network_down\"")
})
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
DispatchQueue.main.async {
if !self.parent.data.isInit {
self.parent.data.isInit = true
}
}
}
}
Here the ContentView:
import SwiftUI
struct ContentView: View {
#StateObject var webViewData = WebViewData()
#ObservedObject var networkManager = NetworkManager()
var body: some View {
ZStack{
Color.white
VStack {
ExecuteCode {
print(networkManager.isConnected)
webViewData.functionCaller.send() //???
}
WebView(data: webViewData)
if (!networkManager.isConnected) {
//???
}
}
.onReceive(webViewData.$parsedText, perform: { parsedText in
if let parsedText = parsedText {
print(parsedText)
}
})
}
}
}
struct ExecuteCode : View {
init( _ codeToExec: () -> () ) {
codeToExec()
}
var body: some View {
return EmptyView()
}
}
Here the NetworkManager:
import Foundation
import Network
class NetworkManager: ObservableObject {
let monitor = NWPathMonitor()
let queue = DispatchQueue(label: "NetworkManager")
#Published var isConnected = true
var imageName: String {
return isConnected ? "wifi" : "wifi.slash"
}
var connectionDescription: String {
if isConnected {
return "Internet connection looks good!"
} else {
return "It looks like you're not connected to the internet. Make sure WiFi is enabled and try again"
}
}
init() {
monitor.pathUpdateHandler = { path in
DispatchQueue.main.async {
if path.status == .satisfied {
self.isConnected = true
} else {
self.isConnected = false
}
}
}
monitor.start(queue: queue)
}
}
Thanks for helping.
Pointers to good docs are very appreciated.

Window webkit failure postMessage iOS Swift

I am trying to create a library that open a web view in a class, where the objetive is opened it but does not see it in the screen. I created and try to execute a javaScript that makes a postMessage, to obtain the result for example in the method userContentController(), but I am getting and error when I execute it. Also, I am used webView.callAsyncJavaScript, cause then I want to execute some async functions that webView.evaluateJavaScript does not let. Some knows what could be the reason that can help me. Thanks.
Code:
import WebKit
class FingerprintJs : NSObject, WKScriptMessageHandler, WKNavigationDelegate{
public func tryFinger(){
getWebView()
}
public func userContentController(_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage){
print("userContentController")
print(message.name)
}
private func getWebView(){
if let viewController = UIWindow.key?.rootViewController {
if Thread.isMainThread {
let webView = self.makeWebView(viewController: viewController as! ViewController)
webView.loadHTMLString(getHtml(), baseURL: nil)
if #available(iOS 14.0, *) {
print("bunea")
webView.callAsyncJavaScript(getJavaScript()!, arguments: [:], in: nil, in: .defaultClient, completionHandler:{(result) in
print("queeeeee")
print(result as Any)
})
} else {
print("Llegaste aca? porqueee ")
// Fallback on earlier versions
}
// webView.evaluateJavaScript(getJavaScript()!,completionHandler:{(result , error) in
// if error == nil {
// print("success")
// print(result as Any)
// }
// else {
// print("error in executing js ", error!)
// }
// })
} else {
print("Llegaste aca? ")
}
}else{
NSLog("The UIApplication keyWindow of the rootViewController must be loaded first", "")
}
}
private func makeWebView(viewController: ViewController) -> WKWebView {
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "message5")
config.preferences = preferences
let webView = WKWebView(frame: .init(x: 1.0, y: 1.0, width: 0, height: 0),
configuration: config)
webView.translatesAutoresizingMaskIntoConstraints = false
viewController.view.addSubview(webView)
return webView
}
private func getJavaScript() -> String?{
var javaScript: String? = nil
if let jsSourcePath = Bundle.main.path(forResource: "ejemplo", ofType: "js") {
do {
javaScript = try String(contentsOfFile: jsSourcePath)
}
catch {
NSLog("getJavaScriptString: "+(error.localizedDescription), "")
}
}
return javaScript
}
private func getHtml() -> String {
let html: String =
"""
<html>
<body>
<script>
</script>
</body>
</html>
"""
return html
}
private var webView: WKWebView? {
didSet {
oldValue?.removeFromSuperview()
oldValue?.configuration.userContentController.removeScriptMessageHandler(forName: "message5")
}
}
}
extension UIWindow {
static var key: UIWindow? {
if #available(iOS 13, *) {
return UIApplication.shared.windows.first { $0.isKeyWindow }
} else {
return UIApplication.shared.keyWindow
}
}
}
Javascript:
testFunct();
function testFunct() {
window.webkit.messageHandlers.message5.postMessage("nice" )
}
Error:
failure(Error Domain=WKErrorDomain Code=4 "A JavaScript exception occurred" UserInfo={WKJavaScriptExceptionLineNumber=0, WKJavaScriptExceptionMessage=TypeError: undefined is not an object (evaluating 'window.webkit.messageHandlers'), WKJavaScriptExceptionColumnNumber=0, NSLocalizedDescription=A JavaScript exception occurred})
In addition for example here I have some changes of code but for example I never receive in method userContentController the message. Where I set the window.webkit.messageHandlers in the html.
import WebKit
import UIKit
class FingerprintJs : NSObject, WKScriptMessageHandler, WKNavigationDelegate{
public func tryFinger(){
getWebView()
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
assert(message.name == "myMessageName")
}
private func getWebView(){
if let viewController = UIWindow.key?.rootViewController {
if Thread.isMainThread {
let webView = self.makeWebView(viewController: viewController as! ViewController)
webView.loadHTMLString(getHtml(), baseURL: nil)
// webView.evaluateJavaScript(getJavaScript()!,completionHandler:{(result , error) in
// if error == nil {
// print("success")
// print(result as Any)
// }
// else {
// print("error in executing js ", error!)
// }
// })
} else {
print("Llegaste aca? ")
}
}else{
NSLog("The UIApplication keyWindow of the rootViewController must be loaded first", "")
}
}
private func makeWebView(viewController: ViewController) -> WKWebView {
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "myMessageName")
config.preferences = preferences
let webView = WKWebView(frame: .init(x: 1.0, y: 1.0, width: 0, height: 0),
configuration: config)
webView.translatesAutoresizingMaskIntoConstraints = false
viewController.view.addSubview(webView)
webView.navigationDelegate = self
return webView
}
private func getJavaScript() -> String?{
var javaScript: String? = nil
if let jsSourcePath = Bundle.main.path(forResource: "ejemplo", ofType: "js") {
do {
javaScript = try String(contentsOfFile: jsSourcePath)
}
catch {
NSLog("getJavaScriptString: "+(error.localizedDescription), "")
}
}
return javaScript
}
private func getHtml() -> String {
let html: String =
"""
<html>
<body>
<script>
window.webkit.messageHandlers.myMessageName.postMessage("message")
</script>
</body>
</html>
"""
return html
}
private var webView: WKWebView? {
didSet {
oldValue?.removeFromSuperview()
oldValue?.configuration.userContentController.removeScriptMessageHandler(forName: "message5")
}
}
}
extension UIWindow {
static var key: UIWindow? {
if #available(iOS 13, *) {
return UIApplication.shared.windows.first { $0.isKeyWindow }
} else {
return UIApplication.shared.keyWindow
}
}
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("testFunct()")
}
try calling the JS method from didFinish navigation method once the webView is loaded. It might be due to calling the JS method before the webView is properly loaded.

Why do the XMLHttpRequest open and send not called using javascript in Android?

I am trying to intercept a POST request before opening an HTML page using webView.
I followed the tutorial on: https://medium.com/#madmuc/intercept-all-network-traffic-in-webkit-on-android-9c56c9262c85
which injects the javascript file through a .js file in the assets folder:
XMLHttpRequest.prototype.origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
// these will be the key to retrieve the payload
this.recordedMethod = method;
this.recordedUrl = url;
this.origOpen(method, url, async, user, password);
};
XMLHttpRequest.prototype.origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(body) {
// interceptor is a Kotlin interface added in WebView
if(body) recorder.recordPayload(this.recordedMethod, this.recordedUrl, body);
this.origSend(body);
};
However, the open and send are never called, I added logs to test and if I move the recorder.recordPayload(...) (which is the method in the javascript interface) out of those functions then recordPayload is called, so the problem is not with the interface or reading from the file.
webView class:
class TestWebView : WebView {
private var urlReturned = false
private var postbackUrl: String? = "https://www.xxxxx.com"
private var postbackHandled = false```
lateinit var recorder: PayloadRecorder
lateinit var payload: String
constructor(context: Context?) : super(context) {
initUI()
}
private fun initUI() {
if (!isInEditMode) {
settings.javaScriptEnabled = true
settings.builtInZoomControls = true
settings.displayZoomControls = false
settings.loadWithOverviewMode = true
settings.useWideViewPort = true
recorder = PayloadRecorder()
addJavascriptInterface(recorder, "recorder")
evaluateJavascript(
context.assets.open("override.js").reader().readText(), null
)
}
val client: WebViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
): WebResourceResponse? {
val payload = recorder.getPayload(request.method, request.url.toString())
// handle the request with the given payload and return the response
return super.shouldInterceptRequest(view, request)
}
override fun onPageStarted(
view: WebView,
url: String,
favicon: Bitmap?
) {
Log.i("testURL", url)
evaluateJavascript(
context.assets.open("override.js").reader().readText(),
object : ValueCallback<String> {
override fun onReceiveValue(value: String?) {
Log.i("onReceiveValue", value)
}
}
)
}
}
setWebChromeClient(object : WebChromeClient() {
override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean {
Log.d("WebView", consoleMessage.message())
return true
}
})
}
constructor(context: Context?, attrs: AttributeSet?) : super(
context,
attrs
) {
initUI()
}
constructor(
context: Context?,
attrs: AttributeSet?,
defStyle: Int
) : super(context, attrs, defStyle) {
initUI()
}
constructor(
context: Context?,
attrs: AttributeSet?,
defStyle: Int,
privateBrowsing: Boolean
) : super(context, attrs, defStyle) {
initUI()
}
class PayloadRecorder {
private val payloadMap: MutableMap<String, String> =
mutableMapOf()
#JavascriptInterface
fun recordPayload(
method: String,
url: String,
payload: String
) {
payloadMap["$method-$url"] = payload
Log.i("payloadRecorder", "recordPayLoad")
}
fun getPayload(
method: String,
url: String
): String? =
payloadMap["$method-$url"]
}
/**
* The function that is called and starts the html progress
*/
#JvmOverloads
fun process(
acsUrl: String?,
md: String?,
paReq: String?,
postbackUrl: String? = null
) {
urlReturned = false
postbackHandled = false
if (!TextUtils.isEmpty(postbackUrl)) {
this.postbackUrl = postbackUrl
}
val postParams: String
payload = paReq?: ""
postParams = //my code
postUrl(acsUrl, postParams.toByteArray())
}
}
recordPayload is never called therefore in shouldInterceptRequest payload always comes back null.

javascript does not work on iOS9+Swift 2.0

I have been using WKWebView.
I would like to work javascript on iOS9+Swift 2.0.
But It couldn't work.
console.log("hoge") couldn't even work.
My code is here
import UIKit
class CommonWebVCL: GABase,UIWebViewDelegate,UIScrollViewDelegate,WKNavigationDelegate,WKUIDelegate {
var webview: WKWebView!
var str_url:String! = ""
var url:String!
var gobackSearch:Bool! = false
var previousScrollViewYOffset:CGFloat? = 0
var toolbarheight:CGFloat = 0
var analytics_category:String!
override func viewDidLoad() {
super.viewDidLoad()
changeUserAgent()
let webViewConfiguration = WKWebViewConfiguration()
self.webview = WKWebView(frame: self.view.bounds, configuration: webViewConfiguration)
self.webview.navigationDelegate = self
self.webview.UIDelegate = self
addObserverForWKWebView()
self.view.addSubview(webview)
setBackButton()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
navigationBarInit()
//タブの遷移でdelegateが外れるので貼り直す
self.webview.scrollView.delegate = self
}
func setBackBtnForWeb(){
let leftbtn = UIButton(frame: CGRect(x: 0, y: 0, width: 48, height: 22))
leftbtn.setImage(UIImage(named: "back_btn"), forState: .Normal)
leftbtn.addTarget(self, action: "goBackForWeb:", forControlEvents: UIControlEvents.TouchUpInside)
let backButton = UIBarButtonItem(customView: leftbtn)
self.navigationItem.setLeftBarButtonItem(backButton, animated: true)
}
func webView(webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!){
// インジケータを表示する
if let tmpstr_url = self.webview.URL?.absoluteString{
if (tmpstr_url.rangeOfString("tel") == nil){
SVProgressHUD.show()
}
}
}
func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!){
// インジケータを表示する
if SVProgressHUD.isVisible(){
SVProgressHUD.dismiss()
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
navigationBarInit()
let URL = NSURL(string: url)
let urlRequest: NSURLRequest = NSURLRequest(URL: URL!)
self.webview.loadRequest(urlRequest)
}
override func viewDidLayoutSubviews() {
self.webview.frame = self.view.bounds
navigationBarInit()
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
if SVProgressHUD.isVisible(){
SVProgressHUD.dismiss()
}
if (self.gobackSearch != nil){
sendEvent(analytics_category, str_action: "back")
self.navigationController?.popViewControllerAnimated(true)
}
self.webview.scrollView.delegate = nil
self.webview.navigationDelegate = nil
self.webview.stopLoading()
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
func addObserverForWKWebView(){
self.webview.addObserver(self, forKeyPath: "canGoBack", options: NSKeyValueObservingOptions.New, context: nil) // WEBページで戻ることができるか監視のため、Observerに追加
self.webview.addObserver(self, forKeyPath: "URL", options: NSKeyValueObservingOptions.New, context: nil) // WEBページで戻ることができるか監視のため、Observerに追加
}
// Observerで監視対象のプロパティに変更があったときの処理
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if object!.isEqual(webview){
if keyPath == "estimatedProgress"{
self.navigationController?.setSGProgressMaskWithPercentage(Float(self.webview.estimatedProgress*100))
navigationBarInit()
}else if keyPath == "canGoBack"{
}else if keyPath == "URL"{
if let tmpstr_url = self.webview.URL?.absoluteString{
self.str_url = tmpstr_url
handleUrl()
}
}
}
}
//URLを見て何かするところ
func handleUrl(){
}
// 画面を閉じるときにObserverを削除。削除しないとアプリ落ちる
deinit{
if self.webview != nil{
//self.webview.removeObserver(self, forKeyPath: "estimatedProgress", context: nil)
self.webview.removeObserver(self, forKeyPath: "canGoBack", context: nil)
self.webview.removeObserver(self, forKeyPath: "URL", context: nil)
}
}
#IBAction func goBackForWeb(sender: AnyObject) {
if self.webview.canGoBack{
webview.goBack()
}else{
self.navigationController?.popViewControllerAnimated(true)
}
}
func webViewDidStartLoad(webView: UIWebView){
SVProgressHUD.showWithStatus("読み込み中")
navigationBarInit()
}
func webViewDidFinishLoad(webView: UIWebView){
SVProgressHUD.dismiss()
}
func webView(webView: UIWebView, didFailLoadWithError error: NSError?){
SVProgressHUD.dismiss()
}
func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void){
let alertController = UIAlertController(title: frame.request.URL?.host, message: message, preferredStyle: .Alert)
alertController.addAction(UIAlertAction(title: "OK", style: .Default, handler: { action in
completionHandler()
}))
self.presentViewController(alertController, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
What is this problem?
By the way this problem is only iOS9 by swift2.0.
It works correctly on other platform including iOS9 by swift 1.2.
I might resolve it by myself.
This web page always use http protocol.
So I changed https protocol,javascript have worked correctly.

Transpile TypeScript In Memory With Node

Is there a way to transpile TypeScript in memory with Node? I would like to be able to get the generated JavaScript in memory.
Yes. TypeScript provides a a ts.transpileModule function:
const ts = require('typescript');
const source = "let x: string = 'hello world'";
const result = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS }});
console.log(result.outputText); // var x = 'hello world';
More
From the TypeScript wiki : https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API#a-simple-transform-function
We ended up developing our own solution based on the native TypeScript transpileModule functionality.
FROM TYPESCRIPT DOCS
TranspileModule will compile source text from 'input' argument using specified compiler options. If no options are provided - it will use a set of default compiler options. Extra compiler options that will unconditionally be used by this function are:
isolatedModules = true
allowNonTsExtensions = true
noLib = true
noResolve = true
transpile() - This code will transpile TypeScript to JavaScript (typescriptServices.min.js from TypeScript is required):
export function transpile(tscode: string): TYPE.EVENT {
interface TranspileOptions {
compilerOptions?: any
fileName?: string;
reportDiagnostics?: boolean;
moduleName?: string;
renamedDependencies?: any;
}
interface TranspileOutput {
outputText: string;
diagnostics?: any[];
sourceMapText?: string;
}
let compilerOptions: ts.CompilerOptions = {
isolatedModules: false
}
let options: TranspileOptions = {
compilerOptions: compilerOptions,
reportDiagnostics: true
// moduleName: modulename
}
let info: TYPE.EVENT;
try {
// Transpile the ts code to js.
let ret: TranspileOutput = ts.transpileModule(tscode, options);
// If diagnostics were returned.
// NOTE: The transpiler is currently always return a message (code=5047) about 'isolatedModules', which
// is not relavent for our use. If there is more than one row than there is in fact an error.
if (ret.diagnostics && ret.diagnostics.length > 0) {
let code = ret.diagnostics[0].code;
if (code == 5047) {
return (info = {
success: true,
returnvalue: ret.outputText,
caller: LibCore.getFunctionName(arguments)
})
} else {
let text = ret.diagnostics[0].messageText;
// let hint = ret.diagnostics[0].file.parseDiagnostics[0].file.nextContainer.symbol.name;
return (info = {
success: false,
returnvalue: ret.diagnostics,
message: `Syntax error: ${text} Code: ${code}`,
caller: LibCore.getFunctionName(arguments)
})
}
} else {
return (info = {
success: true,
returnvalue: ret.outputText,
caller: LibCore.getFunctionName(arguments)
})
}
} catch (e) {
return (info = {
success: false,
message: e.message,
caller: LibCore.getFunctionName(arguments)
})
}
}

Categories

Resources