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