]> BookStack Code Mirror - bookstack/blob - resources/js/services/http.js
Added code editor changes mobile design handling
[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 = {
73             'Content-Type': 'application/json',
74             'X-Requested-With': 'XMLHttpRequest',
75         };
76         options.body = JSON.stringify(data);
77     }
78
79     // Ensure FormData instances are sent over POST
80     // Since Laravel does not read multipart/form-data from other types
81     // of request. Hence the addition of the magic _method value.
82     if (data instanceof FormData && method !== 'post') {
83         data.append('_method', method);
84         options.method = 'post';
85     }
86
87     return request(url, options)
88 }
89
90 /**
91  * Create a new HTTP request, setting the required CSRF information
92  * to communicate with the back-end. Parses & formats the response.
93  * @param {String} url
94  * @param {Object} options
95  * @returns {Promise<{headers: Headers, original: Response, data: (Object|String), redirected: boolean, statusText: string, url: string, status: number}>}
96  */
97 async function request(url, options = {}) {
98     if (!url.startsWith('http')) {
99         url = window.baseUrl(url);
100     }
101
102     if (options.params) {
103         const urlObj = new URL(url);
104         for (let paramName of Object.keys(options.params)) {
105             const value = options.params[paramName];
106             if (typeof value !== 'undefined' && value !== null) {
107                 urlObj.searchParams.set(paramName, value);
108             }
109         }
110         url = urlObj.toString();
111     }
112
113     const csrfToken = document.querySelector('meta[name=token]').getAttribute('content');
114     options = Object.assign({}, options, {
115         'credentials': 'same-origin',
116     });
117     options.headers = Object.assign({}, options.headers || {}, {
118         'baseURL': window.baseUrl(''),
119         'X-CSRF-TOKEN': csrfToken,
120     });
121
122     const response = await fetch(url, options);
123     const content = await getResponseContent(response);
124     const returnData = {
125         data: content,
126         headers: response.headers,
127         redirected: response.redirected,
128         status: response.status,
129         statusText: response.statusText,
130         url: response.url,
131         original: response,
132     };
133
134     if (!response.ok) {
135         throw returnData;
136     }
137
138     return returnData;
139 }
140
141 /**
142  * Get the content from a fetch response.
143  * Checks the content-type header to determine the format.
144  * @param {Response} response
145  * @returns {Promise<Object|String>}
146  */
147 async function getResponseContent(response) {
148     if (response.status === 204) {
149         return null;
150     }
151
152     const responseContentType = response.headers.get('Content-Type') || '';
153     const subType = responseContentType.split(';')[0].split('/').pop();
154
155     if (subType === 'javascript' || subType === 'json') {
156         return await response.json();
157     }
158
159     return await response.text();
160 }
161
162 export default {
163     get: get,
164     post: post,
165     put: put,
166     patch: patch,
167     delete: performDelete,
168 };
Morty Proxy This is a proxified and sanitized view of the page, visit original site.