Cordova/iOS/CBLite Failed API Requests

I’m just trying to make a get request to to the Couchbase API in a skeleton Cordova/iOS application and I am getting a failure response. I’ve tried using coax, jQuery.Ajax and plain XMLHttpRequest and am having the same poor luck.

index.html:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">

  <!-- STYLES -->
  <style>
    @font-face {
      font-family: 'Montserrat';
      src: url('./Fonts/Montserrat-Regular.otf') format("opentype");
    }
  </style>

  <script src="cordova.js"></script>
  <script src="http://code.jquery.com/jquery-2.1.4.min.js" type="text/javascript"></script>
  <script>
    function loadApp() {
      // see http://stackoverflow.com/questions/23662121/cordova-plugins-only-work-in-ios-the-second-time-its-opened-with-a-thread-warnin
      // cordova.exec.setJsToNativeBridgeMode(cordova.exec.jsToNativeModes.XHR_NO_PAYLOAD);

      cbliteTest();

      var head = document.getElementsByTagName('head')[0];
      var script = document.createElement('script');
      script.type = 'text/javascript';
      script.src = 'main.js';
      head.appendChild(script);
    }

    function cbliteTest() {
      if (!window.cblite) {
        console.log('CBLite plug-in not found');
        return;
      }

      console.log('CBLite plug-in found');

      window.cblite.getURL(function (err, url) {
        if (err) {
          console.log('Unable to find CBLite API: ' + JSON.stringify(err));
          return;
        }

        console.log('CBLite API found at: ' + url);

        $.ajax({
          type: 'GET',
          url: url + '_all_dbs',
          beforeSend: function(xhr, contents) {
            console.log('beforeSend-xhr: ' + JSON.stringify(xhr));
            console.log('beforeSend-contents: ' + JSON.stringify(contents));
          },
          success: function (data, status, xhr) {
            console.log('success-xhr: ' + JSON.stringify(xhr));
          },
          error: function (xhr, status, error) {
            console.log('error-xhr: ' + JSON.stringify(xhr));
          }
        });

        //var xhr = new XMLHttpRequest();
        //xhr.open('GET', url);
        //xhr.onreadystatechange = function () {
        //  console.log('State: ' + xhr.readyState);
        //};
        //xhr.onload = function () {
        //  console.log('Success: ' + JSON.stringify(xhr));
        //};
        //xhr.onerror = function () {
        //  console.log('Error: ' + JSON.stringify(xhr));
        //};
        //xhr.send();
      });
    }

    document.addEventListener("deviceready", loadApp, false);
  </script>
</head>
<body style="font-family: 'Montserrat';">
  <div id="app"><!-- CONTENT --></div>
  <!-- DATA -->
</body>
</html>

When I run the application in XCode, I get the following feedback:

2015-06-03 12:28:02.447 Smart Kennel[13445:412742] DiskCookieStorage changing policy from 2 to 0, cookie file: file:///Users/Stephen/Library/Developer/CoreSimulator/Devices/6DE60716-CAFD-4981-9F62-6C0AA5534C50/data/Containers/Data/Application/A104152D-BEA9-4476-B7F5-007C4BA98DC8/Library/Cookies/Cookies.binarycookies
2015-06-03 12:28:02.471 Smart Kennel[13445:412742] Apache Cordova native platform version 3.8.0 is starting.
2015-06-03 12:28:02.472 Smart Kennel[13445:412742] Multi-tasking -> Device: YES, App: YES
2015-06-03 12:28:02.473 Smart Kennel[13445:412742] Unlimited access to network resources
2015-06-03 12:28:02.599 Smart Kennel[13445:412742] Using a WKWebView
2015-06-03 12:28:02.698 Smart Kennel[13445:412742] [CDVTimer][statusbar] 13.449013ms
2015-06-03 12:28:02.698 Smart Kennel[13445:412742] Launching Couchbase Lite...
2015-06-03 12:28:02.704 Smart Kennel[13445:412742] Couchbase Lite url = http://lite.couchbase./
2015-06-03 12:28:02.704 Smart Kennel[13445:412742] [CDVTimer][cblite] 6.072998ms
2015-06-03 12:28:02.705 Smart Kennel[13445:412742] [CDVTimer][TotalPluginStartup] 19.801021ms
[INFO] GCDWebServer started on port 12344 and reachable at http://10.12.0.107:12344/
2015-06-03 12:28:02.707 Smart Kennel[13445:412742] Started http daemon: http://10.12.0.107:12344/ 
2015-06-03 12:28:03.063 Smart Kennel[13445:412742] CBLite plug-in found
2015-06-03 12:28:03.067 Smart Kennel[13445:412742] CBLite API found at: http://lite.couchbase./
2015-06-03 12:28:03.070 Smart Kennel[13445:412742] beforeSend-xhr: {"readyState":0}
2015-06-03 12:28:03.073 Smart Kennel[13445:412742] beforeSend-contents: {"url":"http://lite.couchbase./_all_dbs","type":"GET","isLocal":false,"global":true,"processData":true,"async":true,"contentType":"application/x-www-form-urlencoded; charset=UTF-8","accepts":{"*":"*/*","text":"text/plain","html":"text/html","xml":"application/xml, text/xml","json":"application/json, text/javascript","script":"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},"contents":{"xml":{},"html":{},"json":{},"script":{}},"responseFields":{"xml":"responseXML","text":"responseText","json":"responseJSON"},"converters":{"text html":true},"flatOptions":{"url":true,"context":true},"jsonp":"callback","dataTypes":["*"],"crossDomain":true,"hasContent":false}
2015-06-03 12:28:03.269 Smart Kennel[13445:412742] error-xhr: {"readyState":0,"status":0,"statusText":"error"}
2015-06-03 12:28:03.272 Smart Kennel[13445:412742] error-error: ""

I have found a few instances of people reporting similar results in various forums, but it seems in most cases they were not getting the correct URL. As far as I can tell, the request url here is legit. Anyone have any ideas?

Hi @smsenesac,

I’m not sure why it’s not working.

You can find an example here.
This getting started proj is using fetch to make the GET /_all_dbs request.
See www/js/index.js.

The app prints the response to the console:

Does this help?

James

Strange. Your example works even if I plug-in jQuery and use $.ajax the same as in my project. I connected to my code using Web Inspector and it gives me a little more info than I was getting from XCode. Specifically, it says that host lite.couchbase. cannot be found. I have no clue where to go from here, but at least there’s hope I suppose.

The Couchbase Lite PhoneGap plugin uses the Couchbase Lite Listener to expose the REST API.
As you noticed, the hostname is lite.couchbase.. I believe this was a change in the 1.1 release to make it possible to discover the URL over bluetooth (for P2P use cases).
The lite.couchbase. hostname can only be resolved on the device/simulator where the Couchbase Lite Listener is running.
However, I understand that for PhoneGap development it’s better to have an IPv4 address as the hostname or maybe a .local hostname that actually resolves in the browser (that way, it’s possible to run the app in Chrome/Safari and debug from there).

I pushed a small change to the getting started proj in the PhoneGap plugin to use the CBLListener with a specified port number (on my computer the url is http://Jamess-MacBook-Pro-2.local:59000/ and is accessible from Chrome/Safari…)

Let us know if this works as expected for you and we will fix the PhoneGap plugin accordingly.

Thank you so much, that worked! Once I made those changes, name resolution to the listener started working but I was getting a CORS denial. I added the following in addition to your changes and we have liftoff!

[[CBLManager sharedInstance].customHTTPHeaders setObject:@"*" forKey:@"Access-Control-Allow-Origin"];

Well, that seems to have worked for GET requests, but now I’m having difficulty with PUT. I added the following to CBLite.m and using Web Inspector I can see that those headers are being added to the server responses to GET requests.

[[CBLManager sharedInstance].customHTTPHeaders setObject:@"*" forKey:@"Access-Control-Allow-Origin"];
[[CBLManager sharedInstance].customHTTPHeaders setObject:@"Content-Type" forKey:@"Access-Control-Allow-Headers"];
[[CBLManager sharedInstance].customHTTPHeader

For the OPTIONS request that the browser sends pre-flight for PUT requests, however, the headers are not added and this is causing PUT requests to fail. Is there something else that I should be doing to get those headers into OPTIONS request responses?

Which version of PhoneGap are you using?
If it’s 5.0 and above, using the whitelist plugin might be easier than setting the Access-Control-Allow-Origin header.
See this readme for instructions on using the whitelist plugin https://github.com/couchbaselabs/TodoLite-PhoneGap.

Hmm. That doesn’t seem to be doing much. I added the following to my config.xml and installed the plugin, although I believe the access origin is the only one that should be required for XHR requests.

<access origin="*" />
<allow-navigation href="*" />
<allow-intent href="*" />

My understanding was that since the JavaScript that is making the XHR calls essentially runs in Safari on iOS and that the origin of the requests is http://localhost:12344 and the destination is http://whatever.local:59000 that Access-Control headers were inevitable.

Have you tried using the Disable Local File Restrictions option in Safari?

It looks like you’re having the cross origin request issue during development as the PhoneGap webviews automatically enable CORS.

That doesn’t seem to work either :frowning:

Yes, that is the case. How do other people do in-development debugging under these circumstances?

Actually, I take that back. I believe the reason for this issue is the WKWebView plugin for Cordova. From their docs:

The worst issue is that WKWebView doesn't allow loading files from the file (file://) protocol like UIWebView does. This is what Cordova uses internally to load files from the www folder. Using an embedded local http server to load the www folder from the http protocol circumvents this issue.

Another major pain is that because of using the http protocol WKWebView must now comply with CORS. This means that if your app currently talks to a remote server over http (like a REST API), that server must allow access from your app (see below how to do this).

So, it seems w/o CORS support from the CBLite plugin, these two plugins are not compatible.

Ok thanks for the details. Have you tried running Chrome with CORS disabled:

open -a Google\ Chrome --args --disable-web-security

That way, you can have the CB Listener running in the simulator and make requests to it from the browser which is easier/faster to work with during development.

Hi I am having the same issue. I have also set the same headers, and am running into the CBLite server not responding the fre-flight cors OPTIONS request.

Is there a way to enable this in cbLite?

This is interesting. I am building a react-native app that uses CBLite (great product, BTW!) and am only able to connect to the CBL embedded REST API if I launch a CBLListener with a custom port. Basically, dbmgr.internalURL never really works as a connectable endpoint. I get a “Type Error: Network request failed”.

If I use this Swift code to set up the database, I can use the JS code below to connect:

  func launchCouchbaseLite(view: RCTRootView) {
    println("Launching Couchbase Lite...")
    //CBLManager.enableLogging("CBLListenerVerbose")
    let dbmgr: CBLManager = CBLManager.sharedInstance()
    let listener: CBLListener = CBLListener(manager: dbmgr, port: 59000)
    listener.start(nil)
    view.initialProperties["couchbaseURL"] = listener.URL.absoluteString
    CBLRegisterJSViewCompiler()
    let url: String = view.initialProperties["couchbaseURL"] as! String
    println("Couchbase Lite url = \(url)")
  }

However, if I use this Swift code to set up the database, I cannot use the JS code below to connect:

  func launchCouchbaseLite(view: RCTRootView) {
    println("Launching Couchbase Lite...")
    //CBLManager.enableLogging("CBLListenerVerbose")
    let dbmgr: CBLManager = CBLManager.sharedInstance()
    view.initialProperties["couchbaseURL"] = dbmgr.internalURL.absoluteString
    CBLRegisterJSViewCompiler()
    let url: String = view.initialProperties["couchbaseURL"] as! String
    println("Couchbase Lite url = \(url)")
  }

The javascript (ES6) code I’m using to try to connect is:

  _ensureCouchbaseDB() {
    this.dbURL = `${this.props.couchbaseURL}mindfulworkday/`
    debug("ensuring", this.dbURL)
    fetch(this.dbURL).then((response) => {
      if (response.status !== 200) {
        return fetch(this.dbURL, {method:"PUT"})
        .then((response) => response.json()).then((data) => {
          debug("create db", data)
          return data
        }).catch((error) => {
          debug("create db error", error)
        })
      }
    }).catch((error) => {
      debug("get db error", error)
    })
  }

So, my question is … is it supported to connect to the http://lite.couchbase./ URL? Is there some other way I need to invoke listener.start() when not using a custom port, or should it happen automatically?

Thanks for any insights you can offer!

-Jeffrey