Skip to content

Commit

Permalink
Merge pull request #191 from bocoup/node-fetch-rmeritz
Browse files Browse the repository at this point in the history
Replace deprecated `requests` with `node-fetch`
  • Loading branch information
minicat authored Jul 23, 2020
2 parents c8b9ea2 + 9b52c14 commit 5d3948d
Show file tree
Hide file tree
Showing 11 changed files with 1,728 additions and 1,949 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ To install airtable.js in a node project:

npm install airtable

Airtable.js is compatible with Node 10 and above.

### Browser

Expand All @@ -33,6 +34,8 @@ Edit `test/test_files/index.html` - put your `BASE_ID` and `API_KEY` (Be careful

Then open http://localhost:8000/ in your browser.

Airtable.js is compatible with browsers supported by the Airtable web app.
See the [techincal requirements](https://support.airtable.com/hc/en-us/articles/217990018) for more details.

# Configuration

Expand Down
11 changes: 11 additions & 0 deletions lib/abort-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// istanbul ignore file
if (typeof window === 'undefined') {
module.exports = require('abort-controller');
} else {
if ('signal' in new Request('')) {
module.exports = window.AbortController;
} else {
var polyfill = require('abortcontroller-polyfill/dist/cjs-ponyfill');
module.exports = polyfill.AbortController;
}
}
108 changes: 63 additions & 45 deletions lib/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ var forEach = require('lodash/forEach');
var get = require('lodash/get');
var assign = require('lodash/assign');
var isPlainObject = require('lodash/isPlainObject');
var fetch = require('./fetch');
var AbortController = require('./abort-controller');

// This will become require('xhr') in the browser.
var request = require('request');

var objectToQueryParamString = require('./object_to_query_param_string');
var AirtableError = require('./airtable_error');
var Table = require('./table');
var HttpHeaders = require('./http_headers');
Expand All @@ -34,59 +34,76 @@ Base.prototype.makeRequest = function(options) {

var method = get(options, 'method', 'GET').toUpperCase();

var url =
this._airtable._endpointUrl +
'/v' +
this._airtable._apiVersionMajor +
'/' +
this._id +
get(options, 'path', '/') +
'?' +
objectToQueryParamString(get(options, 'qs', {}));

var controller = new AbortController();

var requestOptions = {
method: method,
url:
this._airtable._endpointUrl +
'/v' +
this._airtable._apiVersionMajor +
'/' +
this._id +
get(options, 'path', '/'),
qs: get(options, 'qs', {}),
headers: this._getRequestHeaders(get(options, 'headers', {})),
json: true,
timeout: this._airtable.requestTimeout,
signal: controller.signal,
};

if ('body' in options && _canRequestMethodIncludeBody(method)) {
requestOptions.body = options.body;
requestOptions.body = JSON.stringify(options.body);
}

var timeout = setTimeout(function() {
controller.abort();
}, this._airtable.requestTimeout);

return new Promise(function(resolve, reject) {
request(requestOptions, function(err, response, body) {
if (!err && response.statusCode === 429 && !that._airtable._noRetryIfRateLimited) {
var numAttempts = get(options, '_numAttempts', 0);
var backoffDelayMs = exponentialBackoffWithJitter(numAttempts);
setTimeout(function() {
var newOptions = assign({}, options, {
_numAttempts: numAttempts + 1,
});
that.makeRequest(newOptions)
.then(resolve)
.catch(reject);
}, backoffDelayMs);
return;
}

if (err) {
fetch(url, requestOptions)
.then(function(resp) {
clearTimeout(timeout);
resp.statusCode = resp.status;
if (resp.status === 429 && !that._airtable._noRetryIfRateLimited) {
var numAttempts = get(options, '_numAttempts', 0);
var backoffDelayMs = exponentialBackoffWithJitter(numAttempts);
setTimeout(function() {
var newOptions = assign({}, options, {
_numAttempts: numAttempts + 1,
});
that.makeRequest(newOptions)
.then(resolve)
.catch(reject);
}, backoffDelayMs);
} else {
resp.json()
.then(function(body) {
var err =
that._checkStatusForError(resp.status, body) ||
_getErrorForNonObjectBody(resp.status, body);

if (err) {
reject(err);
} else {
resolve({
statusCode: resp.status,
headers: resp.headers,
body: body,
});
}
})
.catch(function() {
var err = _getErrorForNonObjectBody(resp.status);
reject(err);
});
}
})
.catch(function(err) {
clearTimeout(timeout);
err = new AirtableError('CONNECTION_ERROR', err.message, null);
} else {
err =
that._checkStatusForError(response.statusCode, body) ||
_getErrorForNonObjectBody(response.statusCode, body);
}

if (err) {
reject(err);
return;
}

resolve({
statusCode: response.statusCode,
headers: response.headers,
body: body,
});
});
});
};

Expand All @@ -100,6 +117,7 @@ Base.prototype._getRequestHeaders = function(headers) {

result.set('Authorization', 'Bearer ' + this._airtable._apiKey);
result.set('User-Agent', userAgent);
result.set('Content-Type', 'application/json');
forEach(headers, function(headerValue, headerKey) {
result.set(headerKey, headerValue);
});
Expand Down
4 changes: 4 additions & 0 deletions lib/fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
var fetch = require('node-fetch');

// istanbul ignore next
module.exports = typeof window === 'undefined' ? fetch : window.fetch;
70 changes: 46 additions & 24 deletions lib/run_action.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
var exponentialBackoffWithJitter = require('./exponential_backoff_with_jitter');
var objectToQueryParamString = require('./object_to_query_param_string');
var packageVersion = require('./package_version');

// This will become require('xhr') in the browser.
var request = require('request');
var fetch = require('./fetch');
var AbortController = require('./abort-controller');

var userAgent = 'Airtable.js/' + packageVersion;

Expand All @@ -24,6 +23,7 @@ function runAction(base, method, path, queryParams, bodyData, callback, numAttem
authorization: 'Bearer ' + base._airtable._apiKey,
'x-api-version': base._airtable._apiVersion,
'x-airtable-application-id': base.getId(),
'content-type': 'application/json',
};
var isBrowser = typeof window !== 'undefined';
// Some browsers do not allow overriding the user agent.
Expand All @@ -34,35 +34,57 @@ function runAction(base, method, path, queryParams, bodyData, callback, numAttem
headers['User-Agent'] = userAgent;
}

var controller = new AbortController();
var normalizedMethod = method.toUpperCase();
var options = {
method: method.toUpperCase(),
url: url,
json: true,
timeout: base._airtable.requestTimeout,
method: normalizedMethod,
headers: headers,
signal: controller.signal,
};

if (bodyData !== null) {
options.body = bodyData;
}

request(options, function(error, resp, body) {
if (error) {
callback(error, resp, body);
return;
if (normalizedMethod === 'GET' || normalizedMethod === 'HEAD') {
console.warn('body argument to runAction are ignored with GET or HEAD requests');
} else {
options.body = JSON.stringify(bodyData);
}
}

if (resp.statusCode === 429 && !base._airtable._noRetryIfRateLimited) {
var backoffDelayMs = exponentialBackoffWithJitter(numAttempts);
setTimeout(function() {
runAction(base, method, path, queryParams, bodyData, callback, numAttempts + 1);
}, backoffDelayMs);
return;
}
var timeout = setTimeout(function() {
controller.abort();
}, base._airtable.requestTimeout);

error = base._checkStatusForError(resp.statusCode, body);
callback(error, resp, body);
});
fetch(url, options)
.then(function(resp) {
clearTimeout(timeout);
if (resp.status === 429 && !base._airtable._noRetryIfRateLimited) {
var backoffDelayMs = exponentialBackoffWithJitter(numAttempts);
setTimeout(function() {
runAction(base, method, path, queryParams, bodyData, callback, numAttempts + 1);
}, backoffDelayMs);
} else {
resp.json()
.then(function(body) {
var error = base._checkStatusForError(resp.status, body);
// Ensure Response interface matches interface from
// `request` Response object
var r = {};
Object.keys(resp).forEach(function(property) {
r[property] = resp[property];
});
r.body = body;
r.statusCode = resp.status;
callback(error, r, body);
})
.catch(function() {
callback(base._checkStatusForError(resp.status));
});
}
})
.catch(function(error) {
clearTimeout(timeout);
callback(error);
});
}

module.exports = runAction;
Loading

0 comments on commit 5d3948d

Please sign in to comment.