ember-fetch icon indicating copy to clipboard operation
ember-fetch copied to clipboard

ember-fetch + fastboot + ember-data sadness

Open stefanpenner opened this issue 8 years ago • 16 comments

fastboot and fetch share the same extension point in ember-data, and today they do not work together.

Long-term, I suspect fastboot should provide a XMLHTTPRequest global, and a fetch global. That way it doesn't interfere in high level code, and would also "just work" with things like pretender/mirage.

stefanpenner avatar Jun 15 '17 17:06 stefanpenner

I'm running into this exact problem now. My app uses fastboot, ember-data, ember-fetch, and I'm in the process of removing jquery and everything is working, except fastboot fetches with the following error:

Error while processing route: index Cannot read property 'catch' of undefined TypeError: Cannot read property 'catch' of undefined
    at Class.ajax (/Users/ron/mirai-audio/mir/tmp/broccoli_merge_trees-output_path-IEMm2qLU.tmp/assets/addon-tree-output/ember-fetch/mixins/adapter-fetch.js:200:1)
    at Class.findAll (/Users/ron/mirai-audio/mir/tmp/broccoli_merge_trees-output_path-IEMm2qLU.tmp/assets/addon-tree-output/modules/ember-data/adapters/rest.js:473:1)
    at _findAll (/Users/ron/mirai-audio/mir/tmp/broccoli_merge_trees-output_path-IEMm2qLU.tmp/assets/addon-tree-output/modules/ember-data/-private.js:8501:1)
    at Class._fetchAll (/Users/ron/mirai-audio/mir/tmp/broccoli_merge_trees-output_path-IEMm2qLU.tmp/assets/addon-tree-output/modules/ember-data/-private.js:10967:1)
    at Class.findAll (/Users/ron/mirai-audio/mir/tmp/broccoli_merge_trees-output_path-IEMm2qLU.tmp/assets/addon-tree-output/modules/ember-data/-private.js:10938:1)
    at Class.model (/Users/ron/mirai-audio/mir/tmp/broccoli_merge_trees-output_path-IEMm2qLU.tmp/assets/mir/routes/index.js:23:1)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)

this line: 192 https://github.com/ember-cli/ember-fetch/blob/fc5167b996b4e4177f8ae920c61a736e13dd14a5/addon/mixins/adapter-fetch.js#L183-L194

0xadada avatar Feb 18 '18 21:02 0xadada

@0xadada you can get around this issue with these instructions.

nlfurniss avatar Mar 02 '18 09:03 nlfurniss

@nlfurniss unfortunately not because I've also removed jQuery in my app as a dependency, and the then().catch() semantics of ember fetch don't yet align with fastboot expectations.

0xadada avatar Mar 02 '18 23:03 0xadada

Did you try adding the initializer to override fastboot per the README?

nlfurniss avatar Mar 07 '18 18:03 nlfurniss

I also ran into an nearly identical issue with ember-data ember-fetch ember-cli-fastboot and ember-simple-auth

Ember-fetch's ajaxOption's does not call super, which results in any other extensions of your application adapter not having the opportunity to set headers or anything like that. The current implementation of ember-fetch's ajaxOptions do not allow this to run.

This breaks ember-simple-auth because the ember-simple-auth's adapter mixin needs to set your auth header.

My workaround is to manually set the headers I need after ember-fetch has run:

  ajaxOptions(...args) {
    const options = this._super(...args);

    get(this, 'session').authorize(get(this, 'authorizer'), (headerName, headerValue) => {
      options.headers[headerName] = headerValue;
    });

    return options;
  },

Duder-onomy avatar Mar 08 '18 21:03 Duder-onomy

@nlfurniss that worked! I hadn't taken that step, because it wasn't in the README back when i first switched to ember-fetch. Thanks!

0xadada avatar Mar 08 '18 22:03 0xadada

I just want to mention that we are moving fetch support to Ember Data itself : https://github.com/emberjs/data/pull/5386

tchak avatar Apr 07 '18 14:04 tchak

@Duder-onomy I'm running into the same issue (using the same libraries) as you explain above. Curious, would you mind elaborating on where and how you manually set those headers?

ehubbell avatar Jun 26 '18 19:06 ehubbell

@ehubbell Totally.

The latest ember-simple-auth has deprecated authorizers... So I had to change some things a few weeks ago. Now that ajaxOptions hook I pasted above looks a little different.

Because we are using basic auth to protect our staging environment, and we are using fastboot, and simple auth uses the 'Authorization' header, and fastboot will not let us change that default... we had to change the name of our access token key to something that does not use Authorization. We used X-Access-Token and had to subsequently change our authorization code in devise to use a diff key.

Here is my entire application adapter:

import DS from 'ember-data';
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';
import AdapterFetch from 'ember-fetch/mixins/adapter-fetch';
import { get } from '@ember/object';
import { inject as service } from '@ember/service';
import config from '../config/environment';

const { JSONAPIAdapter } = DS;

export default JSONAPIAdapter.extend(DataAdapterMixin, AdapterFetch, {
  session: service(),

  authorizer: 'authorizer:application',

  coalesceFindRequests: true,

  host: config.APP.apiBase,
  namespace: config.APP.apiNamespace,

  headers: {
    'X-Requested-with': 'XMLHttpRequest',
  },

  ajaxOptions(...args) {
    const options = this._super(...args);
    const accessToken = get(this, 'session.data.authenticated.access_token');

    options.headers['X-Access-Token'] = accessToken;

    options.headers['Content-Type'] = 'application/vnd.api+json';

    return options;
  },
});

Hope that helps.

Duder-onomy avatar Jun 28 '18 01:06 Duder-onomy

@Duder-onomy Thanks for the help! Turns out I had my fastboot directory inside my app directory hence the issues my ajax service. Once I fixed that and installed something similar to your code above, everything's working.

ehubbell avatar Jul 05 '18 22:07 ehubbell

@mdbiscan yes, here is an example

nlfurniss avatar Aug 22 '18 20:08 nlfurniss

@nlfurniss sorry, hit delete by accident. Thanks for responding. Looks like it's the right track. I have another error to get past, but its related to a DRF adapter mixin.

mdbiscan avatar Aug 22 '18 21:08 mdbiscan

One of my issues is needing to pass HTTPOnly cookies. In my adapter headers property, I had to add this:

if(fastboot.isFastBoot) {
  headers['Cookie'] = fastboot.get('request.headers.cookie');
}

mdbiscan avatar Aug 23 '18 21:08 mdbiscan

Solutions!

@stefanpenner Here is how I've finally solved the problem, i'm also using ember-data with ember-fetch running in an ember-cli-fastboot with ember-simple-auth and have removed jQuery as a dependency.

Here is my entire application adapter:

re: ☝️ @Duder-onomy your application adapter uses authorizers, which has since become a deprecated pattern in ember-simple-auth.

Authorizers and the session service's authorize method are deprecated and will be removed from Ember Simple Auth 2.0. The concept seemed like a good idea in the early days of Ember Simple Auth, but proved to provide limited value for the added complexity. To replace authorizers in an application, simply get the session data from the session service and inject it where needed.

When used with ember-fetch the ajaOptions doesn't work well, and the authorize method will not be called. The headers computed property must be used instead. Here is my application adapter using ember-fetch:

// app/adapters/application.js
import JSONAPIAdapter from 'ember-data/adapters/json-api';
import AdapterFetchMixin from 'ember-fetch/mixins/adapter-fetch';
import config from 'mir/config/environment';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';

export default JSONAPIAdapter.extend(AdapterFetchMixin, {
  session: service(),

  host: config.DS.host,
  namespace: config.DS.namespace,

  headers: computed('session.data.authenticated.token', function() {
    const headers = {};
    headers['Content-Type'] = 'application/vnd.api+json';
    if (this.session.isAuthenticated) {
      const token = this.session.data.authenticated['access_token'];
      headers['Authorization'] = `Bearer ${token}`;
    }
    return headers;
  })
});

I also followed the ember-fetch README, to disable the ajax instance initializer on fastboot:

// fastboot/initializers/ajax.js
export default {
  name: 'ajax-service',
  initialize() {
    // noop
    // This is to override Fastboot's initializer which prevents ember-fetch from working
    // https://github.com/ember-fastboot/ember-cli-fastboot/blob/master/fastboot/initializers/ajax.js
  }
}

One of the nice benefits of this approach is that it helps paves the way to removing jQuery, especially with $.ajax no longer used by ember-data.

0xadada avatar May 06 '19 16:05 0xadada

Note on setting ajaxOptions. I would follow what was recommended in the below . Also, headers as a computed may be another source of a memory leak. Changing to a static getter e.g. get header may have fixed leaking an internal part of Ember.

Just sending out a message in a bottle in case anybody picks it up. Your app may be slightly different, so make sure you have a memory leak before changing from a computed to getter.

Ref - https://github.com/emberjs/data/issues/5061#issuecomment-390778103

snewcomer avatar May 07 '19 03:05 snewcomer

@allthesignals ref issue for container memory leaks

snewcomer avatar Jul 15 '19 14:07 snewcomer