]> BookStack Code Mirror - bookstack/blob - resources/js/services/http.js
Ran eslint fix on existing codebase
[bookstack] / resources / js / services / http.js
1 /**
2  * Perform a HTTP GET request.
3  * Can easily pass query parameters as the second parameter.
4  * @param {String} url
5  * @param {Object} params
6  * @returns {Promise<{headers: Headers, original: Response, data: (Object|String), redirected: boolean, statusText: string, url: string, status: number}>}
7  */
8 async function get(url, params = {}) {
9     return request(url, {
10         method: 'GET',
11         params,
12     });
13 }
14
15 /**
16  * Perform a HTTP POST request.
17  * @param {String} url
18  * @param {Object} data
19  * @returns {Promise<{headers: Headers, original: Response, data: (Object|String), redirected: boolean, statusText: string, url: string, status: number}>}
20  */
21 async function post(url, data = null) {
22     return dataRequest('POST', url, data);
23 }
24
25 /**
26  * Perform a HTTP PUT request.
27  * @param {String} url
28  * @param {Object} data
29  * @returns {Promise<{headers: Headers, original: Response, data: (Object|String), redirected: boolean, statusText: string, url: string, status: number}>}
30  */
31 async function put(url, data = null) {
32     return dataRequest('PUT', url, data);
33 }
34
35 /**
36  * Perform a HTTP PATCH request.
37  * @param {String} url
38  * @param {Object} data
39  * @returns {Promise<{headers: Headers, original: Response, data: (Object|String), redirected: boolean, statusText: string, url: string, status: number}>}
40  */
41 async function patch(url, data = null) {
42     return dataRequest('PATCH', url, data);
43 }
44
45 /**
46  * Perform a HTTP DELETE request.
47  * @param {String} url
48  * @param {Object} data
49  * @returns {Promise<{headers: Headers, original: Response, data: (Object|String), redirected: boolean, statusText: string, url: string, status: number}>}
50  */
51 async function performDelete(url, data = null) {
52     return dataRequest('DELETE', url, data);
53 }
54
55 /**
56  * Perform a HTTP request to the back-end that includes data in the body.
57  * Parses the body to JSON if an object, setting the correct headers.
58  * @param {String} method
59  * @param {String} url
60  * @param {Object} data
61  * @returns {Promise<{headers: Headers, original: Response, data: (Object|String), redirected: boolean, statusText: string, url: string, status: number}>}
62  */
63 async function dataRequest(method, url, data = null) {
64     const options = {
65         method,
66         body: data,
67     };
68
69     // Send data as JSON if a plain object
70     if (typeof data === 'object' && !(data instanceof FormData)) {
71         options.headers = {
72             'Content-Type': 'application/json',
73             'X-Requested-With': 'XMLHttpRequest',
74         };
75         options.body = JSON.stringify(data);
76     }
77
78     // Ensure FormData instances are sent over POST
79     // Since Laravel does not read multipart/form-data from other types
80     // of request. Hence the addition of the magic _method value.
81     if (data instanceof FormData && method !== 'post') {
82         data.append('_method', method);
83         options.method = 'post';
84     }
85
86     return request(url, options);
87 }
88
89 /**
90  * Create a new HTTP request, setting the required CSRF information
91  * to communicate with the back-end. Parses & formats the response.
92  * @param {String} url
93  * @param {Object} options
94  * @returns {Promise<{headers: Headers, original: Response, data: (Object|String), redirected: boolean, statusText: string, url: string, status: number}>}
95  */
96 async function request(url, options = {}) {
97     if (!url.startsWith('http')) {
98         url = window.baseUrl(url);
99     }
100
101     if (options.params) {
102         const urlObj = new URL(url);
103         for (const paramName of Object.keys(options.params)) {
104             const value = options.params[paramName];
105             if (typeof value !== 'undefined' && value !== null) {
106                 urlObj.searchParams.set(paramName, value);
107             }
108         }
109         url = urlObj.toString();
110     }
111
112     const csrfToken = document.querySelector('meta[name=token]').getAttribute('content');
113     options = {...options, credentials: 'same-origin'};
114     options.headers = {
115         ...options.headers || {},
116         baseURL: window.baseUrl(''),
117         'X-CSRF-TOKEN': csrfToken,
118     };
119
120     const response = await fetch(url, options);
121     const content = await getResponseContent(response);
122     const returnData = {
123         data: content,
124         headers: response.headers,
125         redirected: response.redirected,
126         status: response.status,
127         statusText: response.statusText,
128         url: response.url,
129         original: response,
130     };
131
132     if (!response.ok) {
133         throw new HttpError(response, content);
134     }
135
136     return returnData;
137 }
138
139 /**
140  * Get the content from a fetch response.
141  * Checks the content-type header to determine the format.
142  * @param {Response} response
143  * @returns {Promise<Object|String>}
144  */
145 async function getResponseContent(response) {
146     if (response.status === 204) {
147         return null;
148     }
149
150     const responseContentType = response.headers.get('Content-Type') || '';
151     const subType = responseContentType.split(';')[0].split('/').pop();
152
153     if (subType === 'javascript' || subType === 'json') {
154         return await response.json();
155     }
156
157     return await response.text();
158 }
159
160 class HttpError extends Error {
161
162     constructor(response, content) {
163         super(response.statusText);
164         this.data = content;
165         this.headers = response.headers;
166         this.redirected = response.redirected;
167         this.status = response.status;
168         this.statusText = response.statusText;
169         this.url = response.url;
170         this.original = response;
171     }
172
173 }
174
175 export default {
176     get,
177     post,
178     put,
179     patch,
180     delete: performDelete,
181     HttpError,
182 };