Skip to content

Commit

Permalink
Add support for ember-fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
tchak committed Apr 30, 2018
1 parent 55b4d6b commit 3a42f4a
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 81 deletions.
35 changes: 4 additions & 31 deletions addon/adapters/json-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
/**
@module ember-data
*/
import { get } from '@ember/object';
import { dasherize } from '@ember/string';
import RESTAdapter from "./rest";
import { instrument } from 'ember-data/-debug';
import { pluralize } from 'ember-inflector';

/**
Expand Down Expand Up @@ -155,36 +155,9 @@ const JSONAPIAdapter = RESTAdapter.extend({
@return {Object}
*/
ajaxOptions(url, type, options) {
let hash = this._super(...arguments);

if (hash.contentType) {
hash.contentType = 'application/vnd.api+json';
}

instrument(function() {
hash.converters = {
'text json': function(payload) {
let token = heimdall.start('json.parse');
let json;
try {
json = JSON.parse(payload);
} catch (e) {
json = payload;
}
heimdall.stop(token);
return json;
}
};
});

let beforeSend = hash.beforeSend;
hash.beforeSend = function(xhr) {
xhr.setRequestHeader('Accept', 'application/vnd.api+json');
if (beforeSend) {
beforeSend(xhr);
}
};

options.contentType = 'application/vnd.api+json';
let hash = this._super(url, type, options);
hash.headers['accept'] = 'application/vnd.api+json';
return hash;
},

Expand Down
200 changes: 156 additions & 44 deletions addon/adapters/rest.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
@module ember-data
*/

import $ from 'jquery';
import Ember from 'ember';
import fetch, { Response } from 'fetch';
//FIXME: We need to add a direct export of `serializeQueryParams`
import { serializeQueryParams } from 'ember-fetch/mixins/adapter-fetch';

import { Promise as EmberPromise } from 'rsvp';
import { get, computed } from '@ember/object';
import { getOwner } from '@ember/application';
import { run } from '@ember/runloop';
import { assign } from '@ember/polyfills';
import Adapter from "../adapter";
import {
parseResponseHeaders,
Expand All @@ -30,6 +34,8 @@ import { warn } from '@ember/debug';
import { DEBUG } from '@glimmer/env';

const Promise = EmberPromise;
//FIXME: this is a hack!
const USE_FETCH = !Ember.$;

/**
The REST adapter allows your store to communicate with an HTTP server by
Expand Down Expand Up @@ -977,16 +983,33 @@ const RESTAdapter = Adapter.extend(BuildURLMixin, {
let hash = adapter.ajaxOptions(url, type, options);

return new Promise(function(resolve, reject) {
hash.success = function(payload, textStatus, jqXHR) {
heimdall.stop(token);
let response = ajaxSuccessHandler(adapter, payload, jqXHR, requestData);
run.join(null, resolve, response);
};
hash.error = function(jqXHR, textStatus, errorThrown) {
heimdall.stop(token);
let error = ajaxErrorHandler(adapter, jqXHR, errorThrown, requestData);
run.join(null, reject, error);
};
if (USE_FETCH) {
hash.success = function(response) {
heimdall.stop(token);
determineBodyPromise(response, requestData).then(payload => {
response = fetchSuccessHandler(adapter, payload, response, requestData);
run.join(null, resolve, response);
});
};
hash.error = function(response, errorThrown) {
heimdall.stop(token);
determineBodyPromise(response, requestData).then(payload => {
let error = fetchErrorHandler(adapter, payload, response, errorThrown, requestData);
run.join(null, reject, error);
});
};
} else {
hash.success = function(payload, textStatus, jqXHR) {
heimdall.stop(token);
let response = ajaxSuccessHandler(adapter, payload, jqXHR, requestData);
run.join(null, resolve, response);
};
hash.error = function(jqXHR, textStatus, errorThrown) {
heimdall.stop(token);
let error = ajaxErrorHandler(adapter, jqXHR, errorThrown, requestData);
run.join(null, reject, error);
};
}

adapter._ajax(hash);
}, 'DS: RESTAdapter#ajax ' + type + ' to ' + url);
Expand All @@ -998,7 +1021,7 @@ const RESTAdapter = Adapter.extend(BuildURLMixin, {
@param {Object} options jQuery ajax options to be used for the ajax request
*/
_ajaxRequest(options) {
$.ajax(options);
Ember.$.ajax(options);
},

/**
Expand All @@ -1014,8 +1037,20 @@ const RESTAdapter = Adapter.extend(BuildURLMixin, {
}
},

_fetchRequest(options) {
fetch(options.url, options).then(response => {
if (response.ok) {
options.success(response);
} else {
options.error(response);
}
}).catch(error => options.error(new Response(), error));
},

_ajax(options) {
if (get(this, 'fastboot.isFastBoot')) {
if (USE_FETCH) {
this._fetchRequest(options);
} else if (get(this, 'fastboot.isFastBoot')) {
this._najaxRequest(options);
} else {
this._ajaxRequest(options);
Expand All @@ -1030,43 +1065,33 @@ const RESTAdapter = Adapter.extend(BuildURLMixin, {
@param {Object} options
@return {Object}
*/
ajaxOptions(url, type, options) {
let hash = options || {};
hash.type = type;
hash.dataType = 'json';
hash.context = this;

instrument(function() {
hash.converters = {
'text json': function(payload) {
let token = heimdall.start('json.parse');
let json;
try {
json = JSON.parse(payload);
} catch (e) {
json = payload;
}
heimdall.stop(token);
return json;
}
};
});

if (hash.data && type !== 'GET') {
hash.contentType = 'application/json; charset=utf-8';
hash.data = JSON.stringify(hash.data);
}
ajaxOptions(url, method, options) {
options = assign({
url,
method,
type: method
}, options);

let headers = get(this, 'headers');
if (headers !== undefined) {
hash.beforeSend = function (xhr) {
Object.keys(headers).forEach((key) => xhr.setRequestHeader(key, headers[key]));
};
options.headers = assign({}, options.headers, headers);
} else if (!options.headers) {
options.headers = {};
}
if (options.data && options.type !== 'GET') {
let contentType = options.contentType || 'application/json; charset=utf-8';
options.headers['content-type'] = contentType;
}

hash.url = this._ajaxURL(url);
if (USE_FETCH) {
options = fetchOptions(options, this);
} else {
options = ajaxOptions(options, this);
}

options.url = this._ajaxURL(options.url);

return hash;
return options;
},

_ajaxURL(url) {
Expand Down Expand Up @@ -1238,6 +1263,17 @@ function endsWith(string, suffix) {
}
}

function fetchSuccessHandler(adapter, payload, response, requestData) {
let responseData = fetchResponseData(response);
return ajaxSuccess(adapter, payload, requestData, responseData);
}

function fetchErrorHandler(adapter, payload, response, errorThrown, requestData) {
let responseData = fetchResponseData(response);
responseData.errorThrown = errorThrown;
return ajaxError(adapter, payload, requestData, responseData);
}

function ajaxSuccessHandler(adapter, payload, jqXHR, requestData) {
let responseData = ajaxResponseData(jqXHR);
return ajaxSuccess(adapter, payload, requestData, responseData);
Expand All @@ -1250,6 +1286,14 @@ function ajaxErrorHandler(adapter, jqXHR, errorThrown, requestData) {
return ajaxError(adapter, payload, requestData, responseData);
}

function fetchResponseData(response) {
return {
status: response.status,
textStatus: response.textStatus,
headers: headersToObject(response.headers)
};
}

function ajaxResponseData(jqXHR) {
return {
status: jqXHR.status,
Expand All @@ -1258,4 +1302,72 @@ function ajaxResponseData(jqXHR) {
};
}

function determineBodyPromise(response, requestData) {
return response.text().then(function(payload) {
try {
payload = JSON.parse(payload);
} catch (error) {
if (!(error instanceof SyntaxError)) {
throw error;
}
const status = response.status;
if (response.ok && (status === 204 || status === 205 || requestData.method === 'HEAD')) {
payload = { data: null };
} else {
warn('This response was unable to be parsed as json.', payload);
}
}
return payload;
});
}

function headersToObject(headers) {
let headersObject = {};

if (headers) {
headers.forEach((value, key) => headersObject[key] = value);
}

return headersObject;
}

/**
* Helper function that translates the options passed to `jQuery.ajax` into a format that `fetch` expects.
* @param {Object} _options
* @param {DS.Adapter} adapter
* @returns {Object}
*/
export function fetchOptions(options, adapter) {
options.credentials = 'same-origin';

if (options.data) {
// GET and HEAD requests can't have a `body`
if ((options.method === 'GET' || options.method === 'HEAD')) {
// If no options are passed, Ember Data sets `data` to an empty object, which we test for.
if (Object.keys(options.data).length) {
// Test if there are already query params in the url (mimics jQuey.ajax).
const queryParamDelimiter = options.url.indexOf('?') > -1 ? '&' : '?';
options.url += `${queryParamDelimiter}${serializeQueryParams(options.data)}`;
}
} else {
// NOTE: a request's body cannot be an object, so we stringify it if it is.
// JSON.stringify removes keys with values of `undefined` (mimics jQuery.ajax).
options.body = JSON.stringify(options.data);
}
}

return options;
}

function ajaxOptions(options, adapter) {
options.dataType = 'json';
options.context = adapter;

if (options.data && options.type !== 'GET') {
options.data = JSON.stringify(options.data);
}

return options;
}

export default RESTAdapter;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"ember-cli-string-utils": "^1.0.0",
"ember-cli-test-info": "^1.0.0",
"ember-cli-version-checker": "^2.1.0",
"ember-fetch": "^3.4.5",
"ember-inflector": "^2.0.0",
"ember-runtime-enumerable-includes-polyfill": "^2.0.0",
"git-repo-info": "^1.1.2",
Expand Down
Loading

0 comments on commit 3a42f4a

Please sign in to comment.