This repository has been archived by the owner on Sep 13, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
httpReq.js
193 lines (181 loc) · 5.77 KB
/
httpReq.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
import Cookies from 'js-cookie';
const CSRF_COOKIE_NAME = 'crumb';
const CSRF_TOKEN_HEADER = 'X-CSRF-Token';
const CSRF_SAFE_METHODS = new Set(['get', 'head', 'options', 'trace']); // according to RFC7231
/**
* The response body is automatically parsed according
* to the response content type.
*
* @exports httpReq
* @kind function
*
* @param {string} path - the url path that gets appended to baseUrl
* @param {object} options.body - raw body to be send with req
* @param {object} options.payload - raw JSON payload to be send with req (will overwrite options.body)
* @param {boolean} options.raw - disable parsing of response body, returns raw response
* @param {string} options.baseUrl - base for url, defaults to dw api domain
* @param {*} options - see documentation for window.fetch for additional options
*
* @returns {Promise} promise of parsed response body or raw response
*
* @example
* import httpReq from '@datawrapper/shared/httpReq';
* let res = await httpReq('/v3/charts', {
* method: 'post',
* payload: {
* title: 'My new chart'
* }
* });
* import { post } from '@datawrapper/shared/httpReq';
* res = await post('/v3/charts', {
* payload: {
* title: 'My new chart'
* }
* });
* // send raw csv
* await httpReq.put(`/v3/charts/${chartId}/data`, {
* body: csvData,
* headers: {
* 'Content-Type': 'text/csv'
* }
* });
*/
export default function httpReq(path, options = {}) {
if (!options.fetch) {
try {
options.fetch = window.fetch;
} catch (e) {
throw new Error('Neither options.fetch nor window.fetch is defined.');
}
}
if (!options.baseUrl) {
try {
options.baseUrl = `//${window.dw.backend.__api_domain}`;
} catch (e) {
throw new Error('Neither options.baseUrl nor window.dw is defined.');
}
}
const { payload, baseUrl, fetch, raw, ...opts } = {
payload: null,
raw: false,
method: 'GET',
mode: 'cors',
credentials: 'include',
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
};
const url = `${baseUrl.replace(/\/$/, '')}/${path.replace(/^\//, '')}`;
if (payload) {
// overwrite body
opts.body = JSON.stringify(payload);
}
let promise;
if (!CSRF_SAFE_METHODS.has(opts.method.toLowerCase())) {
const csrfCookieValue = Cookies.get(CSRF_COOKIE_NAME);
if (csrfCookieValue) {
opts.headers[CSRF_TOKEN_HEADER] = csrfCookieValue;
promise = fetch(url, opts);
} else {
promise = httpReq('/v3/me', { fetch, baseUrl })
.then(() => {
const csrfCookieValue = Cookies.get(CSRF_COOKIE_NAME);
if (csrfCookieValue) {
opts.headers[CSRF_TOKEN_HEADER] = csrfCookieValue;
}
})
.catch(() => {}) // Ignore errors from /v3/me. It probably means the user is not logged in.
.then(() => fetch(url, opts));
}
} else {
promise = fetch(url, opts);
}
// The variable `promise` and the repeated `fetch(url, opts)` could be replaced with `await
// httpReq('/v3/me'...)`, but then we would need to configure babel to transform async/await for
// all repositories that use @datawrapper/shared.
return promise.then(res => {
if (raw) return res;
if (!res.ok) throw new HttpReqError(res);
if (res.status === 204 || !res.headers.get('content-type')) return res; // no content
// trim away the ;charset=utf-8 from content-type
const contentType = res.headers.get('content-type').split(';')[0];
if (contentType === 'application/json') {
return res.json();
}
if (contentType === 'image/png' || contentType === 'application/pdf') {
return res.blob();
}
// default to text for all other content types
return res.text();
});
}
/**
* Like `httpReq` but with fixed http method GET
* @see {@link httpReq}
*
* @exports httpReq.get
* @kind function
*/
export const get = (httpReq.get = httpReqVerb('GET'));
/**
* Like `httpReq` but with fixed http method PATCH
* @see {@link httpReq}
*
* @exports httpReq.patch
* @kind function
*/
export const patch = (httpReq.patch = httpReqVerb('PATCH'));
/**
* Like `httpReq` but with fixed http method PUT
* @see {@link httpReq}
*
* @exports httpReq.put
* @kind function
*/
export const put = (httpReq.put = httpReqVerb('PUT'));
/**
* Like `httpReq` but with fixed http method POST
* @see {@link httpReq}
*
* @exports httpReq.post
* @kind function
*/
export const post = (httpReq.post = httpReqVerb('POST'));
/**
* Like `httpReq` but with fixed http method HEAD
* @see {@link httpReq}
*
* @exports httpReq.head
* @kind function
*/
export const head = (httpReq.head = httpReqVerb('HEAD'));
/**
* Like `httpReq` but with fixed http method DELETE
* @see {@link httpReq}
*
* @exports httpReq.delete
* @kind function
*/
httpReq.delete = httpReqVerb('DELETE');
function httpReqVerb(method) {
return (path, options) => {
if (options && options.method) {
throw new Error(
`Setting option.method is not allowed in httpReq.${method.toLowerCase()}()`
);
}
return httpReq(path, { ...options, method });
};
}
class HttpReqError extends Error {
constructor(res) {
super();
this.name = 'HttpReqError';
this.status = res.status;
this.statusText = res.statusText;
this.message = `[${res.status}] ${res.statusText}`;
this.response = res;
}
}