kturney / ember-mapbox-gl

Ember integration for Mapbox GL JS
https://kturney.github.io/ember-mapbox-gl
MIT License
33 stars 25 forks source link

Integration with ember-cli-mirage #53

Closed allthesignals closed 4 years ago

allthesignals commented 6 years ago

I would like to setup ember-cli-mirage such that all XHR requests emitted from core mapbox-gl work with the Mirage passthrough API.

Here's what I've found so far:

  1. MapboxGL's request for the style JSON object throws an "AJAXError (200)" through PretenderJS and one of its dependencies, FakeXMLHttpRequest.
  2. Addressing (but maybe not solving) 1 gets us past the XHR request, but lands us into another issue: style JSON appears to be requested twice, throwing a Mapbox error: "There is already a source with this ID." (Full stack trace below).

image

This is a pretty open-ended issue because I'm not sure where to look next. I don't understand why MapboxGL would be request the style JSON twice. Is this because of how Pretender's FakeXHR object is structured? Or is something happening in Ember Mirage?

You can take a peek at how I landed with this at this branch/commit here: https://github.com/allthesignals/ember-mirage-mapbox-example/commit/ce9847380aa96c00082ddf5d73597b9ce52f9b47. yarn and ember s and errors will be right there in console.

Thanks!

Full stack trace server.js:22 Passthrough request: GET https://api.mapbox.com/styles/v1/mapbox/dark-v9?access_token=pk.eyJ1IjoibWF0dGdhcmRuZXJueWMiLCJhIjoiY2o4NTk2dTlzMGFjZDMydWRtY3NsYnFjZSJ9.lA_A4IHM2EGToFiPKFzSug server.js:22 Passthrough request: GET https://api.mapbox.com/v4/mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v7.json?secure&access_token=pk.eyJ1IjoibWF0dGdhcmRuZXJueWMiLCJhIjoiY2o4NTk2dTlzMGFjZDMydWRtY3NsYnFjZSJ9.lA_A4IHM2EGToFiPKFzSug server.js:22 Passthrough request: GET https://api.mapbox.com/styles/v1/mapbox/dark-v9/sprite@2x.json?access_token=pk.eyJ1IjoibWF0dGdhcmRuZXJueWMiLCJhIjoiY2o4NTk2dTlzMGFjZDMydWRtY3NsYnFjZSJ9.lA_A4IHM2EGToFiPKFzSug server.js:22 Passthrough request: GET https://api.mapbox.com/styles/v1/mapbox/dark-v9/sprite@2x.png?access_token=pk.eyJ1IjoibWF0dGdhcmRuZXJueWMiLCJhIjoiY2o4NTk2dTlzMGFjZDMydWRtY3NsYnFjZSJ9.lA_A4IHM2EGToFiPKFzSug mapbox-gl-dev.js:27626 Uncaught Error: There is already a source with this ID at Style.addSource (mapbox-gl-dev.js:27626) at Style._load (mapbox-gl-dev.js:27361) at mapbox-gl-dev.js:27332 at FakeRequest.xhr.onload (mapbox-gl-dev.js:39703) at dispatchEvent (pretender.js:223) at XMLHttpRequest.xhr.(:4200/anonymous function) (http://localhost:4200/assets/vendor.js:108440:9) addSource @ mapbox-gl-dev.js:27626 _load @ mapbox-gl-dev.js:27361 (anonymous) @ mapbox-gl-dev.js:27332 xhr.onload @ mapbox-gl-dev.js:39703 dispatchEvent @ pretender.js:223 xhr.(anonymous function) @ pretender.js:231 load (async) createHandler @ pretender.js:229 createPassthrough @ pretender.js:247 send @ pretender.js:171 exports.getJSON @ mapbox-gl-dev.js:39708 loadURL @ mapbox-gl-dev.js:27328 setStyle @ mapbox-gl-dev.js:37965 Map @ mapbox-gl-dev.js:37369 _setup @ mapbox-gl.js:49 invoke @ backburner.js:205 flush @ backburner.js:125 flush @ backburner.js:278 end @ backburner.js:410 _run @ backburner.js:760 _join @ backburner.js:736 join @ backburner.js:477 run.join @ ember-metal.js:4366 (anonymous) @ ember-metal.js:4441 mightThrow @ jquery.js:3534 process @ jquery.js:3602 setTimeout (async) (anonymous) @ jquery.js:3640 fire @ jquery.js:3268 fireWith @ jquery.js:3398 fire @ jquery.js:3406 fire @ jquery.js:3268 fireWith @ jquery.js:3398 ready @ jquery.js:3878 completed @ jquery.js:3888 mapbox-gl-dev.js:2349 Uncaught AssertionError {name: "AssertionError", actual: false, expected: true, operator: "==", message: "false == true", …}actual: falseexpected: truegeneratedMessage: truemessage: "false == true"name: "AssertionError"operator: "=="stack: "AssertionError: false == true↵ at ImageManager.addImage (http://localhost:4200/assets/vendor.js:78787:7)↵ at http://localhost:4200/assets/vendor.js:91040:45↵ at maybeComplete (http://localhost:4200/assets/vendor.js:89998:13)↵ at http://localhost:4200/assets/vendor.js:89974:13↵ at Image.img.onload (http://localhost:4200/assets/vendor.js:103425:17)"__proto__: Error fail @ mapbox-gl-dev.js:2349 ok @ mapbox-gl-dev.js:2369 addImage @ mapbox-gl-dev.js:15117 (anonymous) @ mapbox-gl-dev.js:27370 maybeComplete @ mapbox-gl-dev.js:26328 (anonymous) @ mapbox-gl-dev.js:26304 img.onload @ mapbox-gl-dev.js:39755 load (async) (anonymous) @ mapbox-gl-dev.js:39754 xhr.onload @ mapbox-gl-dev.js:39724 dispatchEvent @ pretender.js:223 xhr.(anonymous function) @ pretender.js:231 load (async) createHandler @ pretender.js:229 createPassthrough @ pretender.js:247 send @ pretender.js:171 exports.getArrayBuffer @ mapbox-gl-dev.js:39733 exports.getImage @ mapbox-gl-dev.js:39748 module.exports @ mapbox-gl-dev.js:26300 _load @ mapbox-gl-dev.js:27365 (anonymous) @ mapbox-gl-dev.js:27332 xhr.onload @ mapbox-gl-dev.js:39703 (anonymous) @ fake_xml_http_request.js:137 dispatchEvent @ fake_xml_http_request.js:181 dispatchEvent @ pretender.js:221 xhr.(anonymous function) @ pretender.js:231 load (async) createHandler @ pretender.js:229 createPassthrough @ pretender.js:247 send @ pretender.js:171 exports.getJSON @ mapbox-gl-dev.js:39708 loadURL @ mapbox-gl-dev.js:27328 setStyle @ mapbox-gl-dev.js:37965 Map @ mapbox-gl-dev.js:37369 _setup @ mapbox-gl.js:49 invoke @ backburner.js:205 flush @ backburner.js:125 flush @ backburner.js:278 end @ backburner.js:410 _run @ backburner.js:760 _join @ backburner.js:736 join @ backburner.js:477 run.join @ ember-metal.js:4366 (anonymous) @ ember-metal.js:4441 mightThrow @ jquery.js:3534 process @ jquery.js:3602 setTimeout (async) (anonymous) @ jquery.js:3640 fire @ jquery.js:3268 fireWith @ jquery.js:3398 fire @ jquery.js:3406 fire @ jquery.js:3268 fireWith @ jquery.js:3398 ready @ jquery.js:3878 completed @ jquery.js:3888 server.js:22 Passthrough request: GET https://api.mapbox.com/fonts/v1/mapbox/DIN Offc Pro Medium,Arial Unicode MS Regular/0-255.pbf?access_token=pk.eyJ1IjoibWF0dGdhcmRuZXJueWMiLCJhIjoiY2o4NTk2dTlzMGFjZDMydWRtY3NsYnFjZSJ9.lA_A4IHM2EGToFiPKFzSug server.js:22 Passthrough request: GET https://api.mapbox.com/fonts/v1/mapbox/DIN Offc Pro Regular,Arial Unicode MS Regular/0-255.pbf?access_token=pk.eyJ1IjoibWF0dGdhcmRuZXJueWMiLCJhIjoiY2o4NTk2dTlzMGFjZDMydWRtY3NsYnFjZSJ9.lA_A4IHM2EGToFiPKFzSug mapbox-gl-dev.js:14878 Uncaught TypeError: Cannot read property 'stack' of undefined at mapbox-gl-dev.js:14878 at mapbox-gl-dev.js:42151 at mapbox-gl-dev.js:14866 at mapbox-gl-dev.js:14856 at mapbox-gl-dev.js:26267 at XMLHttpRequest.xhr.onload (mapbox-gl-dev.js:39724) at FakeRequest. (fake_xml_http_request.js:137) at FakeRequest.dispatchEvent (fake_xml_http_request.js:181) at dispatchEvent (pretender.js:221) at XMLHttpRequest.xhr.(:4200/anonymous function) (http://localhost:4200/assets/vendor.js:108440:9) (anonymous) @ mapbox-gl-dev.js:14878 (anonymous) @ mapbox-gl-dev.js:42151 (anonymous) @ mapbox-gl-dev.js:14866 (anonymous) @ mapbox-gl-dev.js:14856 (anonymous) @ mapbox-gl-dev.js:26267 xhr.onload @ mapbox-gl-dev.js:39724 (anonymous) @ fake_xml_http_request.js:137 dispatchEvent @ fake_xml_http_request.js:181 dispatchEvent @ pretender.js:221 xhr.(anonymous function) @ pretender.js:231 load (async) createHandler @ pretender.js:229 createPassthrough @ pretender.js:247 send @ pretender.js:171 exports.getArrayBuffer @ mapbox-gl-dev.js:39733 module.exports @ mapbox-gl-dev.js:26255 (anonymous) @ mapbox-gl-dev.js:14846 (anonymous) @ mapbox-gl-dev.js:42148 exports.asyncAll @ mapbox-gl-dev.js:42147 getGlyphs @ mapbox-gl-dev.js:14813 getGlyphs @ mapbox-gl-dev.js:28243 receive @ mapbox-gl-dev.js:39605
allthesignals commented 6 years ago

When I set breakpoints in the Style.addSource prototype, the first time it's called, ID is "composite", second time, "composite".

allthesignals commented 6 years ago

In mapbox-gl, this gets called twice with the same data:

        __chunk1_js.getJSON(request, function (error, json) {
            if (error) {
                this$1.fire(new __chunk1_js.ErrorEvent(error));
            } else if (json) {
                this$1._load((json     ), validate);
            }
        });

So, I think somehow this callback isn't working because PretenderJS is doing something else? Not sure.

kturney commented 6 years ago

if you put a breakpoint here https://github.com/pretenderjs/FakeXMLHttpRequest/blob/master/src/fake-xml-http-request.js#L173, are there multiple listeners? I'm wondering if somehow the callback is getting doubled up

allthesignals commented 6 years ago

Hmm I checked - the length of the listeners array is always either 0 or 1

allthesignals commented 6 years ago

This seems to work:

EDIT: Here's where I actually landed, still very sloppy, but it does not break tests:

Cache the native XMLHttpRequest here: https://github.com/NYCPlanning/labs-applicantmaps/blob/develop/app/app.js#L6

Before a request is made, swap in the FakeXMLHttpRequest: https://github.com/NYCPlanning/labs-applicantmaps/blob/develop/app/adapters/application.js#L6-L8

~Use the mapbox transformRequest hook to swap in the native XMLHttpRequest:~ ~https://github.com/NYCPlanning/labs-applicantmaps/blob/develop/app/components/mapbox-gl.js#L15-L20~

Create an initializer to cache the XMLHttpRequest: https://github.com/NYCPlanning/labs-applicantmaps/blob/31e4cfc5ceaf63e6d12ed3a22de83870c9246a54/app/initializers/fake-xml-http-req.js#L2-L4

kturney commented 6 years ago

in my app I've ended up with this monkeypatch that I call as the first line in my mirage config function. Seems to be working.

// this can be removed if https://github.com/pretenderjs/FakeXMLHttpRequest/pull/32 is ever merged
// and maybe also if a double calling of load event onload is fixed?

export default () => {
  // Note: the below XMLHttpRequest has already been converted to a FakeXMLHttpRequest by pretender

  const origSend = window.XMLHttpRequest.prototype.send;
  window.XMLHttpRequest.prototype.send = function send() {
    origSend.apply(this, arguments);

    const fakeXhr = this; // eslint-disable-line consistent-this
    const realXhr = this._passthroughRequest;
    if (realXhr) {
      realXhr.onload = function(event) {
        if (fakeXhr.responseType !== 'arraybuffer') {
          fakeXhr.response = realXhr.response;
        }

        // dispatch event instead of calling the original to prevent a double call bug
        fakeXhr.dispatchEvent(event);
      };
    }
  };
};
allthesignals commented 6 years ago

@kturney muuuuch smarter! Target the send method, of course. Nice. I'll give this a try.