hubkit
Advanced tools
Comparing version 1.0.6 to 2.0.0
@@ -8,3 +8,3 @@ module.exports = { | ||
LRUCache: true, | ||
superagent: true, | ||
axios: true, | ||
Promise: false, | ||
@@ -11,0 +11,0 @@ setTimeout: false |
{ | ||
"name": "hubkit", | ||
"version": "1.0.6", | ||
"version": "2.0.0", | ||
"homepage": "https://github.com/pkaminski/hubkit", | ||
@@ -24,4 +24,5 @@ "authors": [ | ||
"dependencies": { | ||
"superagent": "5.x" | ||
"axios": "^0.19.2", | ||
"lru-cache": "^4.0.0" | ||
} | ||
} |
204
hubkit.js
if (typeof require !== 'undefined') { | ||
/* global require */ | ||
if (typeof superagent === 'undefined') superagent = require('superagent'); | ||
if (typeof axios === 'undefined') axios = require('axios'); | ||
if (typeof LRUCache === 'undefined') LRUCache = require('lru-cache'); | ||
@@ -29,3 +29,2 @@ } | ||
} | ||
superagent.parse['text/plain'] = function(o) {return o;}; | ||
})(function(isNode) { | ||
@@ -81,3 +80,3 @@ 'use strict'; | ||
Hubkit.defaults = { | ||
method: 'get', host: 'https://api.github.com', perPage: 100, allPages: true, maxTries: 3, | ||
method: 'GET', host: 'https://api.github.com', perPage: 100, allPages: true, maxTries: 3, | ||
maxItemSizeRatio: 0.1, metadata: Hubkit, stats: new Hubkit.Stats(), agent: false, | ||
@@ -98,3 +97,3 @@ corsSuccessFlags: {} | ||
path = interpolatePath(path, options); | ||
var req; | ||
var cachedItem = null, cacheKey, cacheable = options.cache && options.method === 'GET'; | ||
@@ -130,5 +129,5 @@ if (cacheable) { | ||
function handleError(error, res) { | ||
error.request = {method: req.method, url: req.url, headers: res && res.req._headers}; | ||
error.request = {method: options.method, url: path, headers: res && res.config.headers}; | ||
if (error.request.headers) delete error.request.headers.authorization; | ||
if (cacheable && error.status) { | ||
if (cacheable && res && res.status) { | ||
options.cache.del(cacheKey); | ||
@@ -141,3 +140,3 @@ if (options.stats) options.stats.record(false); | ||
if (cacheable && | ||
/Origin is not allowed by Access-Control-Allow-Origin/.test(error.originalMessage)) { | ||
/Network Error/.test(error.originalMessage)) { | ||
cacheable = false; | ||
@@ -151,3 +150,3 @@ retry(); | ||
if (NETWORK_ERROR_CODES.indexOf(error.code) >= 0 || | ||
[500, 502, 503, 504].indexOf(error.status) >= 0 || | ||
[500, 502, 503, 504].indexOf(res && res.status) >= 0 || | ||
error.originalMessage === 'socket hang up' || | ||
@@ -158,6 +157,6 @@ error.originalMessage === 'Unexpected end of input' | ||
options.agent = false; | ||
} else if (error.status === 403 && res && res.header['retry-after']) { | ||
} else if (res && res.status === 403 && res.headers['retry-after']) { | ||
try { | ||
error.retryDelay = | ||
parseInt(res.header['retry-after'].replace(/[^\d]*$/, ''), 10) * 1000; | ||
parseInt(res.headers['retry-after'].replace(/[^\d]*$/, ''), 10) * 1000; | ||
if (!options.timeout || error.retryDelay < options.timeout) value = Hubkit.RETRY; | ||
@@ -167,7 +166,7 @@ } catch (e) { | ||
} | ||
} else if (error.status === 403 && res && res.header['x-ratelimit-remaining'] === '0' && | ||
res.header['x-ratelimit-reset']) { | ||
} else if (res && res.status === 403 && res.headers['x-ratelimit-remaining'] === '0' && | ||
res.headers['x-ratelimit-reset']) { | ||
try { | ||
error.retryDelay = | ||
Math.max(0, parseInt(res.header['x-ratelimit-reset'], 10) * 1000 - Date.now()); | ||
Math.max(0, parseInt(res.headers['x-ratelimit-reset'], 10) * 1000 - Date.now()); | ||
if (!options.timeout || error.retryDelay < options.timeout) value = Hubkit.RETRY; | ||
@@ -197,4 +196,17 @@ } catch (e) { | ||
timeout = timeout || options.timeout; | ||
req = superagent(options.method, path); | ||
addHeaders(req, options, cachedItem); | ||
var rawData; | ||
var config = { | ||
url: path, | ||
method: options.method, | ||
timeout: timeout || 0, | ||
params: {}, | ||
headers: {}, | ||
transformResponse: [function(data) { | ||
rawData = data; | ||
return data; | ||
}].concat(axios.defaults.transformResponse), | ||
}; | ||
addHeaders(config, options, cachedItem); | ||
// If we're paging through a query, the path contains the full query string already so we | ||
@@ -204,6 +216,11 @@ // need to wipe out any additional query params inserted by addHeaders above. Also, if we | ||
// well. | ||
if (cause === 'page' || options._cause === 'page') req._query = []; | ||
if (timeout) req.timeout(timeout); | ||
if (body) req[options.method === 'GET' ? 'query' : 'send'](body); | ||
req.end(onComplete); | ||
if (cause === 'page' || options._cause === 'page') config.params = {}; | ||
if (body) { | ||
if (options.method === 'GET') config.params = Object.assign(config.params, body); | ||
else config.data = body; | ||
} | ||
axios(config).then(function(res) { | ||
onComplete(res, rawData); | ||
}).catch(onError); | ||
}).catch(function(error) { | ||
@@ -224,30 +241,34 @@ reject(error); | ||
function onComplete(error, res) { | ||
if (error && error.response) { | ||
res = error.response; | ||
error = null; | ||
function onError(error) { | ||
if (error.response) { | ||
return onComplete(error.response); | ||
} | ||
if (/Network Error/.test(error.message) && | ||
(options.corsSuccessFlags[options.host] || | ||
!cacheable && (options.method === 'GET' || options.method === 'HEAD')) | ||
) { | ||
error.message = 'Request terminated abnormally, network may be offline'; | ||
} | ||
error.originalMessage = error.message; | ||
error.message = formatError('Hubkit', error.message); | ||
error.fingerprint = | ||
['Hubkit', options.method, options.pathPattern, error.originalMessage]; | ||
handleError(error); | ||
} | ||
function onComplete(res, rawData) { | ||
extractMetadata(path, res, options.metadata); | ||
if (!error && res.header['access-control-allow-origin']) { | ||
if (res.headers['access-control-allow-origin']) { | ||
options.corsSuccessFlags[options.host] = true; | ||
} | ||
try { | ||
if (error) { | ||
if (/Origin is not allowed by Access-Control-Allow-Origin/.test(error.message) && | ||
(options.corsSuccessFlags[options.host] || | ||
!cacheable && (options.method === 'GET' || options.method === 'HEAD')) | ||
) { | ||
error.message = 'Request terminated abnormally, network may be offline'; | ||
} | ||
error.originalMessage = error.message; | ||
error.message = formatError('Hubkit', error.message); | ||
error.fingerprint = | ||
['Hubkit', options.method, options.pathPattern, error.originalMessage]; | ||
handleError(error, res); | ||
} else if (res.status === 304) { | ||
if (res.status === 304) { | ||
cachedItem.expiry = parseExpiry(res); | ||
if (options.stats) options.stats.record(true, cachedItem.size); | ||
resolve(cachedItem.value); | ||
} else if (!(res.ok || options.boolean && res.notFound && res.body && | ||
res.body.message === 'Not Found') || res.body && !res.body.data && res.body.errors) { | ||
} else if (!(res.status >= 200 && res.status < 300 || options.boolean && | ||
res.status === 404 && res.data && res.data.message === 'Not Found') || | ||
res.data && !res.data.data && res.data.errors) { | ||
if (cacheable) { | ||
@@ -263,6 +284,6 @@ options.cache.del(cacheKey); | ||
var errors = '', rateLimited = false; | ||
if (res.body && res.body.errors) { | ||
if (res.data && res.data.errors) { | ||
errors = []; | ||
for (var i = 0; i < res.body.errors.length; i++) { | ||
var errorItem = res.body.errors[i]; | ||
for (var i = 0; i < res.data.errors.length; i++) { | ||
var errorItem = res.data.errors[i]; | ||
if (errorItem.message) { | ||
@@ -278,6 +299,6 @@ errors.push(errorItem.message); | ||
errors = errors.join('; '); | ||
if (res.body.message) errors = ' (' + errors + ')'; | ||
if (res.data.message) errors = ' (' + errors + ')'; | ||
} | ||
var statusError = new Error( | ||
formatError('GitHub', res.status, (res.body && res.body.message || '') + errors) | ||
formatError('GitHub', res.status, (res.data && res.data.message || '') + errors) | ||
); | ||
@@ -293,3 +314,3 @@ statusError.status = res.status; | ||
} | ||
} else if (options.media === 'raw' && res.type !== 'text/plain') { | ||
} else if (options.media === 'raw' && /^text\/plain *;?/.test(res.headers['content-type'])) { | ||
// retry if github disregards 'raw' | ||
@@ -301,4 +322,4 @@ handleError(new Error( | ||
var nextUrl; | ||
if (res.header.link) { | ||
var match = /<([^>]+?)>;\s*rel="next"/.exec(res.header.link); | ||
if (res.headers.link) { | ||
var match = /<([^>]+?)>;\s*rel="next"/.exec(res.headers.link); | ||
nextUrl = match && match[1]; | ||
@@ -309,7 +330,7 @@ if (nextUrl && !(options.method === 'GET' || options.method === 'HEAD')) { | ||
} | ||
if (!res.body && res.text && /\bformat=json\b/.test(res.header['x-github-media-type'])) { | ||
res.body = JSON.parse(res.text); | ||
if (!res.data && rawData && /\bformat=json\b/.test(res.headers['x-github-media-type'])) { | ||
res.data = JSON.parse(rawData); | ||
} | ||
if (/^https?:\/\/[^/]+\/graphql/.test(path)) { | ||
var data = res.body.data; | ||
var data = res.data.data; | ||
var root = data, rootKeys = []; | ||
@@ -344,3 +365,3 @@ while (true) { | ||
if (!result) { | ||
result = res.body; | ||
result = res.data; | ||
delete root.pageInfo; | ||
@@ -392,16 +413,16 @@ } else { | ||
} else { | ||
result = res.body; | ||
result = res.data; | ||
} | ||
} else if (res.body && ( | ||
Array.isArray(res.body) || Array.isArray(res.body.items) || | ||
Array.isArray(res.body.statuses) | ||
} else if (res.data && ( | ||
Array.isArray(res.data) || Array.isArray(res.data.items) || | ||
Array.isArray(res.data.statuses) | ||
)) { | ||
if (!result) { | ||
result = res.body; | ||
} else if (Array.isArray(res.body) && Array.isArray(result)) { | ||
result = result.concat(res.body); | ||
} else if (Array.isArray(res.body.items) && Array.isArray(result.items)) { | ||
result.items = result.items.concat(res.body.items); | ||
} else if (Array.isArray(res.body.statuses) && Array.isArray(result.statuses)) { | ||
result.statuses = result.statuses.concat(res.body.statuses); | ||
result = res.data; | ||
} else if (Array.isArray(res.data) && Array.isArray(result)) { | ||
result = result.concat(res.data); | ||
} else if (Array.isArray(res.data.items) && Array.isArray(result.items)) { | ||
result.items = result.items.concat(res.data.items); | ||
} else if (Array.isArray(res.data.statuses) && Array.isArray(result.statuses)) { | ||
result.statuses = result.statuses.concat(res.data.statuses); | ||
} else { | ||
@@ -430,18 +451,18 @@ throw new Error(formatError('Hubkit', 'unable to concatenate paged results')); | ||
if (options.boolean) { | ||
result = !!res.noContent; | ||
result = res.status === 204; | ||
} else { | ||
result = | ||
(options.responseType || | ||
res.body && typeof res.body === 'object' && Object.keys(res.body).length) ? | ||
res.body : res.text; | ||
res.data && typeof res.data === 'object' && Object.keys(res.data).length) ? | ||
res.data : rawData; | ||
} | ||
} | ||
if (cacheable) { | ||
var size = res.text ? res.text.length : (res.body ? | ||
(res.body.size || res.body.byteLength) : 1); | ||
var size = rawData ? rawData.length : (res.data ? | ||
(res.data.size || res.data.byteLength) : 1); | ||
if (options.stats) options.stats.record(false, size); | ||
if (res.status === 200 && (res.header.etag || res.header['cache-control']) && | ||
if (res.status === 200 && (res.headers.etag || res.headers['cache-control']) && | ||
size <= options.cache.max * options.maxItemSizeRatio) { | ||
options.cache.set(cacheKey, { | ||
value: result, eTag: res.header.etag, status: res.status, size: size, | ||
value: result, eTag: res.headers.etag, status: res.status, size: size, | ||
expiry: parseExpiry(res) | ||
@@ -463,3 +484,2 @@ }); | ||
return requestPromise; | ||
}; | ||
@@ -534,22 +554,28 @@ | ||
function addHeaders(req, options, cachedItem) { | ||
if (cachedItem && cachedItem.eTag) req.set('If-None-Match', cachedItem.eTag); | ||
if (isNode && req.agent) req.agent(options.agent); | ||
function addHeaders(config, options, cachedItem) { | ||
if (cachedItem && cachedItem.eTag) config.headers['If-None-Match'] = cachedItem.eTag; | ||
if (isNode && options.agent) { | ||
config[/^https:/.test(options.host) ? 'httpsAgent' : 'httpAgent'] = options.agent; | ||
} | ||
if (options.token) { | ||
if (!isNode && (options.method === 'GET' || options.method === 'HEAD')) { | ||
req.query({'access_token': options.token}); | ||
config.params['access_token'] = options.token; | ||
} else { | ||
req.set('Authorization', 'token ' + options.token); | ||
config.headers['Authorization'] = 'token ' + options.token; | ||
} | ||
} else if (options.username && options.password) { | ||
req.auth(options.username, options.password); | ||
config.auth = { | ||
username: options.username, | ||
password: options.password | ||
}; | ||
} else if (options.clientId && options.clientSecret) { | ||
req.query({'client_id': options.clientId, 'client_secret': options.clientSecret}); | ||
config.params['client_id'] = options.clientId; | ||
config.params['client_secret'] = options.clientSecret; | ||
} | ||
if (options.userAgent) req.set('User-Agent', options.userAgent); | ||
if (options.media) req.accept('application/vnd.github.' + options.media); | ||
if (options.userAgent) config.headers['User-Agent'] = options.userAgent; | ||
if (options.media) config.headers['Accept'] = 'application/vnd.github.' + options.media; | ||
if (options.method === 'GET' || options.method === 'HEAD') { | ||
req.query({per_page: options.perPage}); // eslint-disable-line camelcase | ||
config.params['per_page'] = options.perPage; | ||
} | ||
if (!isNode && options.responseType) req.responseType(options.responseType); | ||
if (!isNode && options.responseType) config.responseType = options.responseType; | ||
// Work around Firefox bug that forces caching. We can't use Cache-Control because it's not | ||
@@ -560,3 +586,3 @@ // allowed by Github's cross-domain request headers, and because we want to keep our requests | ||
if (!isNode && (options.method === 'GET' || options.method === 'HEAD')) { | ||
req.query({'_nocache': Math.round(Math.random() * 1000000)}); | ||
config.params['_nocache'] = Math.round(Math.random() * 1000000); | ||
} | ||
@@ -569,11 +595,11 @@ } | ||
(/^https?:\/\/[^/]+\/graphql/.test(path) ? 'graphRateLimit' : 'rateLimit'); | ||
metadata[rateName] = res.header['x-ratelimit-limit'] && | ||
parseInt(res.header['x-ratelimit-limit'], 10); | ||
metadata[rateName + 'Remaining'] = res.header['x-ratelimit-remaining'] && | ||
parseInt(res.header['x-ratelimit-remaining'], 10); | ||
metadata[rateName] = res.headers['x-ratelimit-limit'] && | ||
parseInt(res.headers['x-ratelimit-limit'], 10); | ||
metadata[rateName + 'Remaining'] = res.headers['x-ratelimit-remaining'] && | ||
parseInt(res.headers['x-ratelimit-remaining'], 10); | ||
// Not every response includes an X-OAuth-Scopes header, so keep the last known set if | ||
// missing. | ||
if ('x-oauth-scopes' in res.header) { | ||
if ('x-oauth-scopes' in res.headers) { | ||
metadata.oAuthScopes = []; | ||
var scopes = (res.header['x-oauth-scopes'] || '').split(/\s*,\s*/); | ||
var scopes = (res.headers['x-oauth-scopes'] || '').split(/\s*,\s*/); | ||
if (!(scopes.length === 1 && scopes[0] === '')) { | ||
@@ -591,3 +617,3 @@ // GitHub will sometimes return duplicate scopes in the list, so uniquefy them. | ||
function parseExpiry(res) { | ||
var match = (res.header['cache-control'] || '').match(/(^|[,\s])max-age=(\d+)/); | ||
var match = (res.headers['cache-control'] || '').match(/(^|[,\s])max-age=(\d+)/); | ||
if (match) return Date.now() + 1000 * parseInt(match[2], 10); | ||
@@ -594,0 +620,0 @@ } |
{ | ||
"name": "hubkit", | ||
"version": "1.0.6", | ||
"version": "2.0.0", | ||
"description": "GitHub API library for JavaScript, promise-based, for both NodeJS and the browser", | ||
@@ -29,4 +29,4 @@ "main": "index.js", | ||
"dependencies": { | ||
"lru-cache": "^4.0.0", | ||
"superagent": "^5.0.5" | ||
"axios": "^0.19.2", | ||
"lru-cache": "^4.0.0" | ||
}, | ||
@@ -33,0 +33,0 @@ "devDependencies": { |
42001
740
+ Addedaxios@^0.19.2
+ Addedaxios@0.19.2(transitive)
+ Addedfollow-redirects@1.5.10(transitive)
- Removedsuperagent@^5.0.5
- Removedasynckit@0.4.0(transitive)
- Removedcall-bind@1.0.7(transitive)
- Removedcombined-stream@1.0.8(transitive)
- Removedcomponent-emitter@1.3.1(transitive)
- Removedcookiejar@2.1.4(transitive)
- Removeddebug@4.3.5(transitive)
- Removeddefine-data-property@1.1.4(transitive)
- Removeddelayed-stream@1.0.0(transitive)
- Removedes-define-property@1.0.0(transitive)
- Removedes-errors@1.3.0(transitive)
- Removedfast-safe-stringify@2.1.1(transitive)
- Removedform-data@3.0.1(transitive)
- Removedformidable@1.2.6(transitive)
- Removedfunction-bind@1.1.2(transitive)
- Removedget-intrinsic@1.2.4(transitive)
- Removedgopd@1.0.1(transitive)
- Removedhas-property-descriptors@1.0.2(transitive)
- Removedhas-proto@1.0.3(transitive)
- Removedhas-symbols@1.0.3(transitive)
- Removedhasown@2.0.2(transitive)
- Removedinherits@2.0.4(transitive)
- Removedmethods@1.1.2(transitive)
- Removedmime@2.6.0(transitive)
- Removedmime-db@1.52.0(transitive)
- Removedmime-types@2.1.35(transitive)
- Removedms@2.1.2(transitive)
- Removedobject-inspect@1.13.2(transitive)
- Removedqs@6.12.1(transitive)
- Removedreadable-stream@3.6.2(transitive)
- Removedsafe-buffer@5.2.1(transitive)
- Removedsemver@7.6.2(transitive)
- Removedset-function-length@1.2.2(transitive)
- Removedside-channel@1.0.6(transitive)
- Removedstring_decoder@1.3.0(transitive)
- Removedsuperagent@5.3.1(transitive)
- Removedutil-deprecate@1.0.2(transitive)