1 | const browser = typeof window !== 'undefined';
|
2 | const querystring = require('querystring');
|
3 | const Package = require('../package');
|
4 | const transport = browser ? require('./browser') : require('./node');
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | class Snekfetch extends transport.Extension {
|
12 | |
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | |
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 | constructor(method, url, opts = {}) {
|
35 | super();
|
36 | this.options = Object.assign({ version: 1, qs: querystring, followRedirects: true }, opts);
|
37 | this.request = transport.buildRequest.call(this, method, url, opts);
|
38 | if (opts.query) this.query(opts.query);
|
39 | if (opts.data) this.send(opts.data);
|
40 | }
|
41 |
|
42 | |
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 | query(name, value) {
|
49 | this._checkModify();
|
50 | if (!this.request.query) this.request.query = {};
|
51 | if (name !== null && typeof name === 'object') {
|
52 | for (const [k, v] of Object.entries(name)) this.query(k, v);
|
53 | } else {
|
54 | this.request.query[name] = value;
|
55 | }
|
56 | return this;
|
57 | }
|
58 |
|
59 | |
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 | set(name, value) {
|
66 | this._checkModify();
|
67 | if (name !== null && typeof name === 'object') {
|
68 | for (const key of Object.keys(name)) this.set(key, name[key]);
|
69 | } else {
|
70 | this.request.setHeader(name, value);
|
71 | }
|
72 | return this;
|
73 | }
|
74 |
|
75 | |
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 | attach(...args) {
|
83 | this._checkModify();
|
84 | const form = this._getFormData();
|
85 | if (typeof args[0] === 'object') {
|
86 | for (const [k, v] of Object.entries(args[0])) this.attach(k, v);
|
87 | } else {
|
88 | form.append(...args);
|
89 | }
|
90 | return this;
|
91 | }
|
92 |
|
93 | |
94 |
|
95 |
|
96 |
|
97 |
|
98 | send(data) {
|
99 | this._checkModify();
|
100 | if (data instanceof transport.FormData || transport.shouldSendRaw(data)) {
|
101 | this.data = data;
|
102 | } else if (data !== null && typeof data === 'object') {
|
103 | const header = this.request.getHeader('content-type');
|
104 | let serialize;
|
105 | if (header) {
|
106 | if (header.includes('json')) serialize = JSON.stringify;
|
107 | else if (header.includes('urlencoded')) serialize = this.options.qs.stringify;
|
108 | } else {
|
109 | this.set('Content-Type', 'application/json');
|
110 | serialize = JSON.stringify;
|
111 | }
|
112 | this.data = serialize(data);
|
113 | } else {
|
114 | this.data = data;
|
115 | }
|
116 | return this;
|
117 | }
|
118 |
|
119 | then(resolver, rejector) {
|
120 | if (this._response) return this._response.then(resolver, rejector);
|
121 |
|
122 | return this._response = transport.finalizeRequest.call(this)
|
123 | .then(({ response, raw, redirect, headers }) => {
|
124 | if (redirect) {
|
125 | let method = this.request.method;
|
126 | if ([301, 302].includes(response.statusCode)) {
|
127 | if (method !== 'HEAD') method = 'GET';
|
128 | this.data = null;
|
129 | } else if (response.statusCode === 303) {
|
130 | method = 'GET';
|
131 | }
|
132 |
|
133 | const redirectHeaders = this.request.getHeaders();
|
134 | delete redirectHeaders.host;
|
135 | return new Snekfetch(method, redirect, {
|
136 | data: this.data,
|
137 | headers: redirectHeaders,
|
138 | });
|
139 | }
|
140 |
|
141 | const statusCode = response.statusCode || response.status;
|
142 |
|
143 | const self = this;
|
144 | |
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 | const res = {
|
156 | request: this.request,
|
157 | get body() {
|
158 | delete res.body;
|
159 | const type = this.headers['content-type'];
|
160 | if (type && type.includes('application/json')) {
|
161 | try {
|
162 | res.body = JSON.parse(res.text);
|
163 | } catch (err) {
|
164 | res.body = res.text;
|
165 | }
|
166 | } else if (type && type.includes('application/x-www-form-urlencoded')) {
|
167 | res.body = self.options.qs.parse(res.text);
|
168 | } else {
|
169 | res.body = raw;
|
170 | }
|
171 |
|
172 | return res.body;
|
173 | },
|
174 | text: raw.toString(),
|
175 | ok: statusCode >= 200 && statusCode < 400,
|
176 | headers: headers || response.headers,
|
177 | status: statusCode,
|
178 | statusText: response.statusText || transport.STATUS_CODES[response.statusCode],
|
179 | };
|
180 |
|
181 | if (res.ok) {
|
182 | return res;
|
183 | } else {
|
184 | const err = new Error(`${res.status} ${res.statusText}`.trim());
|
185 | Object.assign(err, res);
|
186 | return Promise.reject(err);
|
187 | }
|
188 | })
|
189 | .then(resolver, rejector);
|
190 | }
|
191 |
|
192 | catch(rejector) {
|
193 | return this.then(null, rejector);
|
194 | }
|
195 |
|
196 | |
197 |
|
198 |
|
199 |
|
200 |
|
201 | end(cb) {
|
202 | return this.then(
|
203 | (res) => cb ? cb(null, res) : res,
|
204 | (err) => cb ? cb(err, err.status ? err : null) : Promise.reject(err)
|
205 | );
|
206 | }
|
207 |
|
208 | _getFormData() {
|
209 | if (!(this.data instanceof transport.FormData)) {
|
210 | this.data = new transport.FormData();
|
211 | }
|
212 | return this.data;
|
213 | }
|
214 |
|
215 | _finalizeRequest() {
|
216 | if (!this.request) return;
|
217 | if (!this.request.getHeader('user-agent')) {
|
218 | this.set('User-Agent', `snekfetch/${Snekfetch.version} (${Package.homepage})`);
|
219 | }
|
220 | if (this.request.method !== 'HEAD') this.set('Accept-Encoding', 'gzip, deflate');
|
221 | if (this.data && this.data.getBoundary) {
|
222 | this.set('Content-Type', `multipart/form-data; boundary=${this.data.getBoundary()}`);
|
223 | }
|
224 | if (this.request.query) {
|
225 | const [path, query] = this.request.path.split('?');
|
226 | this.request.path = `${path}?${this.options.qs.stringify(this.request.query)}${query ? `&${query}` : ''}`;
|
227 | }
|
228 | }
|
229 |
|
230 | _checkModify() {
|
231 | if (this.response) throw new Error('Cannot modify request after it has been sent!');
|
232 | }
|
233 | }
|
234 |
|
235 | Snekfetch.version = Package.version;
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 |
|
245 | Snekfetch.METHODS = transport.METHODS.concat('BREW').filter((m) => m !== 'M-SEARCH');
|
246 | for (const method of Snekfetch.METHODS) {
|
247 | Snekfetch[method.toLowerCase()] = (url, opts) => new Snekfetch(method, url, opts);
|
248 | }
|
249 |
|
250 | module.exports = Snekfetch;
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|