-
Notifications
You must be signed in to change notification settings - Fork 0
/
fetchUtils.js
219 lines (203 loc) · 6.81 KB
/
fetchUtils.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/* eslint-disable no-console */
const fetch = require('node-fetch')
const urlJoin = require('url-join')
const FormData = require('form-data')
const HEADER_USER_AGENT = 'User-Agent'
const USER_AGENT = 'KTH api-call/4.*'
const MIME_JSON = 'application/json'
const MIME_TEXT = 'text/'
const MIME_SVG = 'image/svg+xml'
const HEADER_CONTENT_LENGTH = 'content-length'
const HEADER_CONTENT_TYPE = 'content-type'
function removeUndefined(obj) {
const returnObj = Object.keys(obj).reduce((acc, key) => {
if (obj[key] === undefined) {
return acc
}
acc[key] = obj[key]
return acc
}, {})
return returnObj
}
async function _parseResponseBody(response, forceBuffer) {
const contentLength = response.headers.get(HEADER_CONTENT_LENGTH)
const contentType = response.headers.get(HEADER_CONTENT_TYPE)
if (forceBuffer) {
return response.buffer()
}
if (contentLength === '0') {
return response.text()
}
if (contentType?.includes(MIME_JSON)) {
const buffer = await response.buffer()
try {
return JSON.parse(buffer.toString())
} catch (error) {
return buffer.toString()
}
}
if (contentType?.includes(MIME_SVG)) {
return response.text()
}
if (contentType?.includes(MIME_TEXT)) {
return response.text()
}
return response.buffer()
}
function buildFormData(data) {
const form = new FormData()
for (const key in data) {
if (key === 'file') {
form.append(key, data[key].value, data[key].options)
} else {
form.append(key, data[key])
}
}
return form
}
function _createFetchWrapper(wrapperOptions, method) {
const { baseUrl = '', headers = {}, json = true } = wrapperOptions
return async (options, callback) => {
headers[HEADER_USER_AGENT] = USER_AGENT
const { uri } = options
const target = urlJoin(baseUrl, uri)
let opts
if (options.formData) {
opts = {}
opts.method = method
const form = buildFormData(options.formData)
opts.body = form
opts.headers = { ...headers, ...options.headers, ...form.getHeaders() }
} else if (json) {
opts = { ...options }
opts.method = method
opts.headers = { ...headers, ...options.headers }
opts.headers[HEADER_CONTENT_TYPE] = MIME_JSON
opts.body = JSON.stringify(opts.body)
} else {
opts = {}
}
const forceBuffer = opts.encoding === null
try {
const response = await fetch(target, opts)
const responseBody = method === 'HEAD' ? undefined : await _parseResponseBody(response, forceBuffer)
response.statusCode = response.status
callback(null, response, responseBody)
} catch (error) {
callback(error)
}
}
}
/**
* Fetch wrapper. Should mimic previous use of request(options, callback).
*
* Options are:
* url:
* (Description in request)
* Fully qualified uri or a parsed url object from url.parse().
*
* Value is created from Api options: 'https', 'host', 'port', and 'path'.
*
* qs:
* (Description in request)
* Object containing querystring values to be appended to the uri.
*
* Value from Api option 'query'. Default is {}.
*
* qsStringifyOptions:
* (Description in request)
* Object containing options to pass to the qs.stringify method.
* Alternatively pass options to the querystring.stringify method using this format {sep:';', eq:':', options:{}}.
* For example, to change the way arrays are converted to query strings using the qs module
* pass the arrayFormat option with one of indices|brackets|repeat
*
* Value from Api option 'qsOptions' Default is { arrayFormat: 'brackets' }. Deprecated.
*
* method:
* (Description in request)
* http method (default: "GET")
*
* Value from Api option 'method'. Default is 'GET'.
*
* json:
* (Description in request)
* Sets body to JSON representation of value and adds Content-type: application/json header.
* Additionally, parses the response body as JSON.
*
* Value from Api option 'json'. Default value is true.
*
* body:
* (Description in request)
* Entity body for PATCH, POST and PUT requests. Must be a Buffer, String or ReadStream.
* If json is true, then body must be a JSON-serializable object.
*
* Value from Api option 'data', through JSON.stringify.
*
* headers:
* (Description in request)
* http headers (default: {})
*
* Value from Api option 'headers'. Default value is {}.
*
* encoding:
* (Description in request)
* Encoding to be used on setEncoding of response data. If null, the body is returned as a Buffer.
* Anything else (including the default value of undefined) will be passed as the encoding parameter to toString() (meaning this is effectively utf8 by default).
* (Note: if you expect binary data, you should set encoding: null.)
*
* Value from Api option 'encoding'. Default value is 'utf8'. Deprecated.
*
* @param {object} options Options formerly passed to request
* @param {function} callback Callback formerly passed to request
*/
async function fetchWrapper(options, callback) {
const { url, qs = {}, method, json, body, headers } = options
headers[HEADER_USER_AGENT] = USER_AGENT
const queryObj = removeUndefined(qs)
if (json) {
headers[HEADER_CONTENT_TYPE] = MIME_JSON
}
const queryStr = new URLSearchParams(queryObj).toString()
const fetchUrl = queryStr ? urlJoin(url, '?' + queryStr) : url
const fetchOptions = {
method,
headers,
body,
}
const forceBuffer = options.encoding === null
try {
const response = await fetch(fetchUrl, fetchOptions)
const responseBody = await _parseResponseBody(response, forceBuffer)
response.statusCode = response.status
callback(null, response, responseBody)
} catch (error) {
callback(error)
}
}
/**
* Fetch wrappers with provided options. Should mimic previous use of request.defaults(options).
*
* Wrapper options are:
* baseUrl: Used to prefix all calls
* headers: Passed on to call header
* json: (from request) Sets body to JSON representation of value and adds Content-type: application/json header. Additionally, parses the response body as JSON.
* pool: Not implemented. Since Node 12 maxSockets are Infinity. (See https://nodejs.org/dist/v0.12.0/docs/api/http.html#http_agent_maxsockets)
*
* @param {object} wrapperOptions Options formerly passed to request.defaults
* @returns Wrapped fetch with options
*/
function fetchWrappers(wrapperOptions = {}) {
return {
get: _createFetchWrapper(wrapperOptions, 'GET'),
post: _createFetchWrapper(wrapperOptions, 'POST'),
put: _createFetchWrapper(wrapperOptions, 'PUT'),
del: _createFetchWrapper(wrapperOptions, 'DELETE'),
head: _createFetchWrapper(wrapperOptions, 'HEAD'),
patch: _createFetchWrapper(wrapperOptions, 'PATCH'),
}
}
module.exports = {
fetchWrapper,
fetchWrappers,
removeUndefined,
}