PluginProbe
ElasticPress / 5.3.5
ElasticPress v5.3.5
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
elasticpress / assets / js / sync / src / hooks.js

hooks.js in ElasticPress 5.3.5, at assets/js/sync/src/hooks.js

237 lines 5.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * WordPress dependencies.
3 */
4 import { useCallback, useRef } from '@wordpress/element';
5 import { __ } from '@wordpress/i18n';
6
7 /**
8 * Indexing hook.
9 *
10 * Provides methods for indexing, getting indexing status, and cancelling
11 * indexing. Methods share an abort controller so that requests can
12 * interrupt eachother to avoid multiple sync requests causing race conditions
13 * or duplicate output, such as by rapidly pausing and unpausing indexing.
14 *
15 * @param {string} apiUrl AJAX endpoint URL.
16 * @param {string} nonce WordPress nonce.
17 * @returns {object} Sync, sync status, and cancel functions.
18 */
19 export const useIndex = (apiUrl, nonce) => {
20 const abort = useRef(new AbortController());
21 const request = useRef(null);
22
23 /**
24 * Extract valid JSON from a response body that may contain stray output.
25 * This is a workaround for plugins that echo the output in the shortcode.
26 *
27 * @param {string} responseBody The raw response body.
28 * @returns {string} The extracted JSON string.
29 */
30 const extractJson = (responseBody) => {
31 const jsonStart = responseBody.indexOf('{"data"');
32
33 // if not found or is at the beginning, return the entire response body.
34 if (jsonStart === -1 || jsonStart === 0) {
35 return responseBody;
36 }
37
38 return responseBody.substring(jsonStart);
39 };
40
41 const onResponse = useCallback(
42 /**
43 * Handle the response to the request.
44 *
45 * @param {Response} response Request response.
46 * @throws {Error} An error for unexpected responses.
47 * @returns {void}
48 */
49 async (response) => {
50 const responseBody = await response.text();
51
52 const errorMessage = `${__(
53 'ElasticPress: Unexpected response.',
54 'elasticpress',
55 )}\n${responseBody}`;
56
57 /**
58 * Throw an error for non-20X responses.
59 */
60 if (!response.ok) {
61 if (response.status === 403) {
62 /**
63 * A 403 response will occur if the nonce has expired or
64 * if the user's session has expired. Reloading the page
65 * will reset the nonce or prompt the user to log in again.
66 */
67 throw new Error(
68 __(
69 'Permission denied. Reload the sync page and try again.',
70 'elasticpress',
71 ),
72 );
73 } else {
74 /**
75 * Log the raw response to the console to assist with
76 * debugging.
77 */
78 console.error(errorMessage); // eslint-disable-line no-console
79
80 /**
81 * Any other response is unexpected, and the user will
82 * need to troubleshoot.
83 */
84 throw new Error(
85 __(
86 'Something went wrong. Find troubleshooting steps at https://www.elasticpress.io/resources/articles/troubleshooting-guide-elasticpress-something-went-wrong-error/.',
87 'elasticpress',
88 ),
89 );
90 }
91 }
92
93 /**
94 * Parse the response and throw an error if it fails.
95 */
96 try {
97 return JSON.parse(extractJson(responseBody));
98 } catch (e) {
99 /**
100 * Log the raw response to the console to assist with
101 * debugging.
102 */
103 console.error(e.message); // eslint-disable-line no-console
104 console.error(errorMessage); // eslint-disable-line no-console
105
106 /**
107 * Invalid JSON is unexpected at this stage, and the user will
108 * need to troubleshoot.
109 */
110 throw new Error(
111 __(
112 'Unable to parse response. Find troubleshooting steps at https://www.elasticpress.io/resources/articles/troubleshooting-guide-elasticpress-something-went-wrong-error/.',
113 'elasticpress',
114 ),
115 );
116 }
117 },
118 [],
119 );
120
121 const onComplete = useCallback(
122 /**
123 * Handle completion of the request, whether successful or not.
124 *
125 * @returns {void}
126 */
127 () => {
128 request.current = null;
129 },
130 [],
131 );
132
133 const sendRequest = useCallback(
134 /**
135 * Send AJAX request.
136 *
137 * Silently catches abort errors and clears the current request on
138 * completion.
139 *
140 * @param {URL} url API URL.
141 * @param {object} options Request options.
142 * @throws {Error} Any non-abort errors.
143 * @returns {Promise} Current request promise.
144 */
145 (url, options) => {
146 request.current = fetch(url, options).then(onResponse).finally(onComplete);
147
148 return request.current;
149 },
150 [onComplete, onResponse],
151 );
152
153 const cancelIndex = useCallback(
154 /**
155 * Send a request to cancel sync.
156 *
157 * @returns {Promise} Fetch request promise.
158 */
159 async () => {
160 abort.current.abort();
161 abort.current = new AbortController();
162
163 const url = new URL(apiUrl);
164
165 const options = {
166 headers: {
167 'X-WP-Nonce': nonce,
168 },
169 method: 'DELETE',
170 signal: abort.current.signal,
171 };
172
173 return sendRequest(url, options);
174 },
175 [apiUrl, nonce, sendRequest],
176 );
177
178 const index = useCallback(
179 /**
180 * Send a request to sync.
181 *
182 * @param {object} args Sync args.
183 * @returns {Promise} Fetch request promise.
184 */
185 async (args) => {
186 abort.current.abort();
187 abort.current = new AbortController();
188
189 const url = new URL(apiUrl);
190
191 Object.keys(args).forEach((arg) => {
192 if (args[arg]) {
193 url.searchParams.append(arg, args[arg]);
194 }
195 });
196
197 const options = {
198 headers: {
199 'X-WP-Nonce': nonce,
200 },
201 method: 'POST',
202 signal: abort.current.signal,
203 };
204
205 return sendRequest(url, options);
206 },
207 [apiUrl, nonce, sendRequest],
208 );
209
210 const indexStatus = useCallback(
211 /**
212 * Send a request for CLI sync status.
213 *
214 * @returns {Promise} Fetch request promise.
215 */
216 async () => {
217 abort.current.abort();
218 abort.current = new AbortController();
219
220 const url = new URL(apiUrl);
221
222 const options = {
223 headers: {
224 'X-WP-Nonce': nonce,
225 },
226 method: 'GET',
227 signal: abort.current.signal,
228 };
229
230 return sendRequest(url, options);
231 },
232 [apiUrl, nonce, sendRequest],
233 );
234
235 return { cancelIndex, index, indexStatus };
236 };
237