PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.0.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.0.0
2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / src / admin / utils / apiClient.js

apiClient.js in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.0.0, at src/admin/utils/apiClient.js

266 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * API Client Utility
3 *
4 * Standardized API client for ThinkRank admin interface.
5 * Provides consistent API interaction patterns extracted from Site Identity
6 * with proper error handling, request/response formatting, and caching.
7 *
8 * @package ThinkRank
9 * @since 1.0.0
10 */
11
12 import apiFetch from '@wordpress/api-fetch';
13 import { __ } from '@wordpress/i18n';
14
15 /**
16 * Create API client for a specific tab/module
17 *
18 * @param {string} basePath Base API path (e.g., 'site-identity', 'homepage-seo')
19 * @param {Object} options Client configuration options
20 * @param {boolean} options.enableCaching Enable response caching (default: false)
21 * @param {number} options.cacheTimeout Cache timeout in milliseconds (default: 300000 - 5 minutes)
22 * @param {boolean} options.enableRetry Enable automatic retry on failure (default: true)
23 * @param {number} options.maxRetries Maximum retry attempts (default: 2)
24 *
25 * @return {Object} API client methods
26 */
27 export const createApiClient = (basePath, options = {}) => {
28 const {
29 enableCaching = false,
30 cacheTimeout = 300000, // 5 minutes
31 enableRetry = true,
32 maxRetries = 2
33 } = options;
34
35 const apiBasePath = `/thinkrank/v1/${basePath}`;
36 const cache = new Map();
37
38 /**
39 * Get cache key for request
40 */
41 const getCacheKey = (endpoint, method, data) => {
42 return `${method}:${endpoint}:${JSON.stringify(data || {})}`;
43 };
44
45 /**
46 * Check if cached response is valid
47 */
48 const isCacheValid = (cacheEntry) => {
49 return cacheEntry && (Date.now() - cacheEntry.timestamp) < cacheTimeout;
50 };
51
52 /**
53 * Enhanced error handling following Site Identity patterns
54 */
55 const handleApiError = (error, endpoint) => {
56 console.error(`API Error [${basePath}${endpoint}]:`, error);
57
58 let errorMessage = __('Request failed. Please try again.', 'thinkrank');
59
60 if (error.message) {
61 if (error.message.includes('API key')) {
62 errorMessage = __('AI optimization requires an API key. Please configure your API key in ThinkRank settings.', 'thinkrank');
63 } else if (error.message.includes('rate limit')) {
64 errorMessage = __('Rate limit reached. Please try again in a few minutes.', 'thinkrank');
65 } else if (error.message.includes('network')) {
66 errorMessage = __('Network error. Please check your connection and try again.', 'thinkrank');
67 } else if (error.message.includes('permission')) {
68 errorMessage = __('Permission denied. Please check your user permissions.', 'thinkrank');
69 }
70 }
71
72 return {
73 success: false,
74 error: errorMessage,
75 originalError: error
76 };
77 };
78
79 /**
80 * Make API request with retry logic
81 */
82 const makeRequest = async (endpoint, options, retryCount = 0) => {
83 try {
84 const response = await apiFetch({
85 path: `${apiBasePath}${endpoint}`,
86 ...options
87 });
88
89 return response;
90 } catch (error) {
91 if (enableRetry && retryCount < maxRetries) {
92 // Wait before retry (exponential backoff)
93 const delay = Math.pow(2, retryCount) * 1000;
94 await new Promise(resolve => setTimeout(resolve, delay));
95 return makeRequest(endpoint, options, retryCount + 1);
96 }
97
98 throw error;
99 }
100 };
101
102 /**
103 * GET request
104 */
105 const get = async (endpoint, params = {}) => {
106 const cacheKey = getCacheKey(endpoint, 'GET', params);
107
108 // Check cache if enabled
109 if (enableCaching && cache.has(cacheKey)) {
110 const cacheEntry = cache.get(cacheKey);
111 if (isCacheValid(cacheEntry)) {
112 return cacheEntry.data;
113 }
114 cache.delete(cacheKey);
115 }
116
117 try {
118 // Add query parameters if provided
119 let fullEndpoint = endpoint;
120 if (Object.keys(params).length > 0) {
121 const queryString = new URLSearchParams(params).toString();
122 fullEndpoint += `?${queryString}`;
123 }
124
125 const response = await makeRequest(fullEndpoint, {
126 method: 'GET'
127 });
128
129 // Cache successful responses
130 if (enableCaching && response.success) {
131 cache.set(cacheKey, {
132 data: response,
133 timestamp: Date.now()
134 });
135 }
136
137 return response;
138 } catch (error) {
139 return handleApiError(error, endpoint);
140 }
141 };
142
143 /**
144 * POST request
145 */
146 const post = async (endpoint, data = {}) => {
147 try {
148 const response = await makeRequest(endpoint, {
149 method: 'POST',
150 data
151 });
152
153 // Clear related cache entries on successful POST
154 if (enableCaching && response.success) {
155 // Clear cache entries that might be affected by this update
156 for (const key of cache.keys()) {
157 if (key.includes(endpoint) || key.includes('GET:')) {
158 cache.delete(key);
159 }
160 }
161 }
162
163 return response;
164 } catch (error) {
165 return handleApiError(error, endpoint);
166 }
167 };
168
169 /**
170 * PUT request
171 */
172 const put = async (endpoint, data = {}) => {
173 try {
174 const response = await makeRequest(endpoint, {
175 method: 'PUT',
176 data
177 });
178
179 // Clear related cache entries on successful PUT
180 if (enableCaching && response.success) {
181 for (const key of cache.keys()) {
182 if (key.includes(endpoint)) {
183 cache.delete(key);
184 }
185 }
186 }
187
188 return response;
189 } catch (error) {
190 return handleApiError(error, endpoint);
191 }
192 };
193
194 /**
195 * DELETE request
196 */
197 const del = async (endpoint) => {
198 try {
199 const response = await makeRequest(endpoint, {
200 method: 'DELETE'
201 });
202
203 // Clear related cache entries on successful DELETE
204 if (enableCaching && response.success) {
205 for (const key of cache.keys()) {
206 if (key.includes(endpoint)) {
207 cache.delete(key);
208 }
209 }
210 }
211
212 return response;
213 } catch (error) {
214 return handleApiError(error, endpoint);
215 }
216 };
217
218 /**
219 * Clear cache
220 */
221 const clearCache = (pattern = null) => {
222 if (!enableCaching) return;
223
224 if (pattern) {
225 for (const key of cache.keys()) {
226 if (key.includes(pattern)) {
227 cache.delete(key);
228 }
229 }
230 } else {
231 cache.clear();
232 }
233 };
234
235 /**
236 * Get cache statistics
237 */
238 const getCacheStats = () => {
239 if (!enableCaching) return null;
240
241 return {
242 size: cache.size,
243 keys: Array.from(cache.keys())
244 };
245 };
246
247 return {
248 get,
249 post,
250 put,
251 delete: del,
252 clearCache,
253 getCacheStats
254 };
255 };
256
257 /**
258 * Default API client for general use
259 */
260 export const apiClient = createApiClient('', {
261 enableCaching: true,
262 enableRetry: true
263 });
264
265 export default createApiClient;
266