From 5e011dbc1104d1d5daf1042704b3d08e3d0b1880 Mon Sep 17 00:00:00 2001 From: Will Nelson Date: Tue, 19 Jun 2018 11:10:55 -0700 Subject: [PATCH] switch to node-fetch (#2587) * switch to node-fetch * remove useless var declaration * remove method uppercasing * rework concurrency * Revert "rework concurrency" This reverts commit ef6aa2697e07277ae9f561f72558f38f358d2e08. * fix headers --- package.json | 3 +- src/rest/APIRequest.js | 37 ++++++++++++++-------- src/rest/handlers/RequestHandler.js | 49 +++++++++++++++-------------- src/util/DataResolver.js | 8 ++--- src/util/Util.js | 19 ++++++----- test/sharder.js | 5 +-- test/webpack.html | 4 +-- 7 files changed, 70 insertions(+), 55 deletions(-) diff --git a/package.json b/package.json index 16d7dfece..eb6cbf33d 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,10 @@ "runkitExampleFilename": "./docs/examples/ping.js", "unpkg": "./webpack/discord.min.js", "dependencies": { + "form-data": "^2.3.2", + "node-fetch": "^2.1.2", "pako": "^1.0.0", "prism-media": "^0.2.0", - "snekfetch": "^3.6.0", "tweetnacl": "^1.0.0", "ws": "^4.0.0" }, diff --git a/src/rest/APIRequest.js b/src/rest/APIRequest.js index e0527320d..ec8375dd1 100644 --- a/src/rest/APIRequest.js +++ b/src/rest/APIRequest.js @@ -1,7 +1,8 @@ const querystring = require('querystring'); -const snekfetch = require('snekfetch'); +const FormData = require('form-data'); const https = require('https'); const { browser, UserAgent } = require('../util/Constants'); +const fetch = require('node-fetch'); if (https.Agent) var agent = new https.Agent({ keepAlive: true }); @@ -17,24 +18,34 @@ class APIRequest { this.path = `${path}${queryString ? `?${queryString}` : ''}`; } - gen() { + make() { const API = this.options.versioned === false ? this.client.options.http.api : `${this.client.options.http.api}/v${this.client.options.http.version}`; + const url = API + this.path; + let headers = {}; - const request = snekfetch[this.method](`${API}${this.path}`, { agent }); - - if (this.options.auth !== false) request.set('Authorization', this.rest.getAuth()); - if (this.options.reason) request.set('X-Audit-Log-Reason', encodeURIComponent(this.options.reason)); - if (!browser) request.set('User-Agent', UserAgent); - if (this.options.headers) request.set(this.options.headers); + if (this.options.auth !== false) headers.Authorization = this.rest.getAuth(); + if (this.options.reason) headers['X-Audit-Log-Reason'] = encodeURIComponent(this.options.reason); + if (!browser) headers['User-Agent'] = UserAgent; + if (this.options.headers) headers = Object.assign(headers, this.options.headers); + let body; if (this.options.files) { - for (const file of this.options.files) if (file && file.file) request.attach(file.name, file.file, file.name); - if (typeof this.options.data !== 'undefined') request.attach('payload_json', JSON.stringify(this.options.data)); - } else if (typeof this.options.data !== 'undefined') { - request.send(this.options.data); + body = new FormData(); + for (const file of this.options.files) if (file && file.file) body.append(file.name, file.file, file.name); + if (typeof this.options.data !== 'undefined') body.append('payload_json', JSON.stringify(this.options.data)); + if (!browser) headers = Object.assign(headers, body.getHeaders()); + } else if (this.options.data != null) { // eslint-disable-line eqeqeq + body = JSON.stringify(this.options.data); + headers['Content-Type'] = 'application/json'; } - return request; + + return fetch(url, { + method: this.method, + headers, + agent, + body, + }); } } diff --git a/src/rest/handlers/RequestHandler.js b/src/rest/handlers/RequestHandler.js index 069386ae0..c3812c92d 100644 --- a/src/rest/handlers/RequestHandler.js +++ b/src/rest/handlers/RequestHandler.js @@ -68,34 +68,37 @@ class RequestHandler { resolve(); } }; - item.request.gen().end((err, res) => { + item.request.make().then(res => { if (res && res.headers) { - if (res.headers['x-ratelimit-global']) this.manager.globallyRateLimited = true; - this.limit = Number(res.headers['x-ratelimit-limit']); - this.resetTime = Date.now() + Number(res.headers['retry-after']); - this.remaining = Number(res.headers['x-ratelimit-remaining']); + if (res.headers.get('x-ratelimit-global')) this.manager.globallyRateLimited = true; + this.limit = Number(res.headers.get('x-ratelimit-limit') || Infinity); + this.resetTime = Number(res.headers.get('x-ratelimit-reset') || 0); + this.remaining = Number(res.headers.get('x-ratelimit-remaining') || 1); } - if (err) { - if (err.status === 429) { - this.queue.unshift(item); - finish(Number(res.headers['retry-after']) + this.client.options.restTimeOffset); - } else if (err.status >= 500 && err.status < 600) { - if (item.retried) { - item.reject(err); - finish(); - } else { - item.retried = true; - this.queue.unshift(item); - finish(1e3 + this.client.options.restTimeOffset); - } - } else { - item.reject(err.status >= 400 && err.status < 500 ? - new DiscordAPIError(res.request.path, res.body, res.request.method) : err); + + if (res.ok) { + res.json().then(item.resolve, item.reject); + finish(); + return; + } + + if (res.status === 429) { + this.queue.unshift(item); + finish(Number(res.headers.get('retry-after')) + this.client.options.restTimeOffset); + } else if (res.status >= 500 && res.status < 600) { + if (item.retried) { + item.reject(res); finish(); + } else { + item.retried = true; + this.queue.unshift(item); + finish(1e3 + this.client.options.restTimeOffset); } } else { - const data = res && res.body ? res.body : {}; - item.resolve(data); + res.json().then(data => { + item.reject(res.status >= 400 && res.status < 500 ? + new DiscordAPIError(item.path, data, item.method) : res); + }, item.reject); finish(); } }); diff --git a/src/util/DataResolver.js b/src/util/DataResolver.js index 336203683..95e0aa5ad 100644 --- a/src/util/DataResolver.js +++ b/src/util/DataResolver.js @@ -1,6 +1,6 @@ const path = require('path'); const fs = require('fs'); -const snekfetch = require('snekfetch'); +const fetch = require('node-fetch'); const Util = require('../util/Util'); const { Error: DiscordError, TypeError } = require('../errors'); const { browser } = require('../util/Constants'); @@ -83,13 +83,13 @@ class DataResolver { * @returns {Promise} */ static resolveFile(resource) { - if (resource instanceof Buffer) return Promise.resolve(resource); + if (!browser && resource instanceof Buffer) return Promise.resolve(resource); if (browser && resource instanceof ArrayBuffer) return Promise.resolve(Util.convertToBuffer(resource)); if (typeof resource === 'string') { if (/^https?:\/\//.test(resource)) { - return snekfetch.get(resource).then(res => res.body instanceof Buffer ? res.body : Buffer.from(res.text)); - } else { + return fetch(resource).then(res => browser ? res.blob() : res.buffer()); + } else if (!browser) { return new Promise((resolve, reject) => { const file = browser ? resource : path.resolve(resource); fs.stat(file, (err, stats) => { diff --git a/src/util/Util.js b/src/util/Util.js index 798db4991..66117e9e3 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -1,6 +1,6 @@ -const snekfetch = require('snekfetch'); const Collection = require('./Collection'); const { Colors, DefaultOptions, Endpoints } = require('./Constants'); +const fetch = require('node-fetch'); const { Error: DiscordError, RangeError, TypeError } = require('../errors'); const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k); const splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^/]+?|)(\.[^./]*|))(?:[/]*)$/; @@ -90,15 +90,14 @@ class Util { * @returns {Promise} The recommended number of shards */ static fetchRecommendedShards(token, guildsPerShard = 1000) { - return new Promise((resolve, reject) => { - if (!token) throw new DiscordError('TOKEN_MISSING'); - snekfetch.get(`${DefaultOptions.http.api}/v${DefaultOptions.http.version}${Endpoints.botGateway}`) - .set('Authorization', `Bot ${token.replace(/^Bot\s*/i, '')}`) - .end((err, res) => { - if (err) reject(err); - resolve(res.body.shards * (1000 / guildsPerShard)); - }); - }); + if (!token) throw new DiscordError('TOKEN_MISSING'); + return fetch(`${DefaultOptions.http.api}/v${DefaultOptions.http.version}${Endpoints.botGateway}`, { + method: 'GET', + headers: { Authorization: `Bot ${token.replace(/^Bot\s*/i, '')}` }, + }).then(res => { + if (res.ok) return res.json(); + throw res; + }).then(data => data.shards * (1000 / guildsPerShard)); } /** diff --git a/test/sharder.js b/test/sharder.js index 9110bcae1..e130128d0 100644 --- a/test/sharder.js +++ b/test/sharder.js @@ -1,7 +1,8 @@ const Discord = require('../'); +const { token } = require('./auth'); -const sharder = new Discord.ShardingManager(`${process.cwd()}/test/shard.js`, 4, false); +const sharder = new Discord.ShardingManager(`${process.cwd()}/test/shard.js`, { token, respawn: false }); sharder.on('launch', shard => console.log(`launched ${shard.id}`)); -sharder.spawn(4); +sharder.spawn(); diff --git a/test/webpack.html b/test/webpack.html index 771b4e4ad..6de81a555 100644 --- a/test/webpack.html +++ b/test/webpack.html @@ -5,7 +5,7 @@ - +