Ionic v1 app and WKWebView CORS limitations - external APIs


#1

Hi there, I’ve been working towards migrating my v1 app to WKWebView. There are several issues I need to figure out a solution for, but here are a few I am hoping others have faced (and solved):

  1. Due to enforced CORS issue, several well known APIs don’t work anymore with browser http. Here are some examples https://api.github.com/repos/pliablepixels/zmNinja/releases/latest as well as https://medium.com/zmninja/latest?format=json - all from popular sites, don’t work due to missing Access-Control-Allow-Origin headers. It’s unreasonable to expect them to insert a localhost:8080 - why would they do that? How have others solved these issues?

  2. I realize there is a cordova-advanced-http plugin that can sidestep this. Is anyone aware of a angularjs wrapper that switches between this and browser $http - my app needs to work in both desktop and mobile. There is a wrapper written by a contributor, but that’s for Angular, not AngularJS. If there isn’t one, can someone point me to some simple template that can help me start writing one? I don’t want to replace all code elements around my codebase. Would prefer to keep $http everywhere

  3. Here is the zinger. Even if I use cordova.plugin.http for regular APIs, I need to use the browser <img src> tag to display images that need to borrow credentials from these API calls. Will it work - because img src always uses a browser http function. I know there are several replacements to img but none of the handle streaming images. Thanks.


#2

Understandably, ionic v1 has more or less become an echo chamber now.
Anyway, I’ve made progress and figured out how to write what I need. It needs cleanup and error/fixing which I will do over the next few days but in general it involves:

  1. using a decorator
  2. Making sure you ignore local template/url/asset requests
  3. parse response to JSON or not, depending on responseType=='text'
  4. Make sure we don’t use .success & .error and use .then() instead
  5. Convert cordova-plugin-http into one that returns promises
  6. encode the URLs before passing it

    // Wraps around $http that switches between browser XHR
    // or cordova-advanced-http based on if cordova is available 
    // credits:
    // a) https://www.exratione.com/2013/08/angularjs-wrapping-http-for-fun-and-profit/
    // b) https://gist.github.com/adamreisnz/354364e2a58786e2be71

    $provide.decorator('$http', ['$delegate', '$q', function($delegate, $q) {
      // create function which overrides $http function
      var $http = $delegate;

      var wrapper = function () {
        var url;
        var method;

         url = arguments[0].url;
         method = arguments[0].method;
        var isOutgoingRequest = /^(http|https):\/\//.test(url);
        if (window.cordova && isOutgoingRequest) {
          console.log ("**** -->"+method+"<-- using native HTTP with:"+url);
          var d = $q.defer();
          var options = {
            method: method,
            data: arguments[0].data,
            headers: arguments[0].headers,
            timeout: arguments[0].timeout,
            responseType: arguments[0].responseType
          };

           cordova.plugin.http.sendRequest(encodeURI(url),options,
            function (succ) {
              // automatic JSON parse if no responseType: text
              // fall back to text if JSON parse fails too
              if (options.responseType =='text') {
                // don't parse into JSON
                d.resolve({"data":succ.data});
                return d.promise;
              }
              else {
                try {
                  d.resolve({"data":JSON.parse(succ.data)});
                  return d.promise;
                }
                catch (e) {
                  d.resolve({"data":succ.data});
                  return d.promise;
                }

              }
            }, 
            function (err) {
              console.log ("***  Inside native HTTP error");
              d.reject(err);
              return d.promise;
            });
            return d.promise;
          
        }
        else { // not cordova, so lets go back to default http
          console.log ("**** "+method+" using XHR HTTP for "+url);
          return $http.apply($http, arguments);
        }

      };

      // wrap around all HTTP methods
      Object.keys($http).filter(function (key) {
        return (typeof $http[key] === 'function');
      }).forEach(function (key) {
        wrapper[key] = function () {
          return $http[key].apply($http, arguments);
        };
      });
    // wrap convenience functions
      $delegate.get = function (url,config) {
       
        return wrapper(angular.extend(config || {}, {
          method: 'get',
          url: url
        }));
      };

      $delegate.post = function (url,data,config) {
       
        return wrapper(angular.extend(config || {}, {
          method: 'post',
          url: url,
          data:data
        }));
      };

      $delegate.delete = function (url,config) {
        return wrapper(angular.extend(config || {}, {
          method: 'delete',
          url: url
        }));
      };

      return wrapper;
    }]);


#3

Great post! Thank you.