Skip to content

Commit 9f7f461

Browse files
authored
Merge pull request #1 from Samagra-Development/sequential-api-requests
[Feat] Sequential API Requests
2 parents bad65a5 + 2e2c2b3 commit 9f7f461

File tree

2 files changed

+170
-32
lines changed

2 files changed

+170
-32
lines changed

package-lock.json

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.tsx

+166-28
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,18 @@ import React, {
66
useContext,
77
useEffect,
88
useState,
9-
useRef
9+
useRef,
1010
} from 'react';
1111
import { DataSyncContext } from './data-sync-context';
1212
import axios from 'axios';
1313
import localForage from 'localforage';
14-
1514
import { omit } from 'underscore';
15+
1616
const api = axios.create();
1717
const RETRY_LIMIT = 0;
1818
const RETRY_DELAY_MS = 1000;
1919
const API_REQUESTS_STORAGE_KEY = 'apiRequests';
20+
const OFFLINE_RESPONSES_STORAGE_KEY = 'offlineResponses';
2021

2122
import {
2223
generateUuid,
@@ -32,21 +33,35 @@ import useNetworkStatus from './hooks/useNetworkStatus';
3233
// const hasWindow = () => {
3334
// return window && typeof window !== 'undefined';
3435
// };
36+
interface ApiRequest {
37+
id: string;
38+
type?: string;
39+
url: string;
40+
method: string;
41+
data?: any | ((previousResponses: any[]) => Promise<any>);
42+
isFormdata?: boolean;
43+
retryCount?: number;
44+
dependsOn?: string;
45+
}
46+
3547
type ConfigType = {
3648
isFormdata?: boolean;
3749
maxRetry?: number;
50+
executionOrder?: string[];
51+
sequentialProcessing?: boolean;
3852
};
53+
3954
export const OfflineSyncProvider: FC<{
4055
children: ReactElement;
4156
render?: (status: { isOffline?: boolean; isOnline: boolean }) => ReactNode;
4257
onStatusChange?: (status: { isOnline: boolean }) => void;
4358
onCallback?: (data: any) => void;
4459
toastConfig?: any;
4560
config?: ConfigType;
46-
}> = ({ children, render, onStatusChange, onCallback }) => {
61+
}> = ({ children, render, onStatusChange, onCallback, config }) => {
4762
// Manage state for data, offline status, and online status
4863
const [data, setData] = useState<Record<string, any>>({});
49-
const isSyncing = useRef<boolean>();
64+
const isSyncing = useRef<boolean>(false);
5065
const [isOnline, setIsOnline] = useState<boolean>(
5166
window?.navigator?.onLine ?? true
5267
);
@@ -61,7 +76,6 @@ export const OfflineSyncProvider: FC<{
6176
handleOnline();
6277
} else {
6378
handleOffline();
64-
6579
}
6680
}
6781
}, [isConnected]);
@@ -101,7 +115,7 @@ export const OfflineSyncProvider: FC<{
101115

102116
const saveRequestToOfflineStorage = async (apiConfig: any) => {
103117
try {
104-
const storedRequests: Array<any> =
118+
const storedRequests: any =
105119
(await localForage.getItem(API_REQUESTS_STORAGE_KEY)) || [];
106120
console.log('perform stored', {
107121
req: storedRequests,
@@ -110,10 +124,17 @@ export const OfflineSyncProvider: FC<{
110124
if (apiConfig?.isFormdata && apiConfig?.data instanceof FormData) {
111125
// console.log({ apiConfig })
112126
const newData = await _formDataToObject(apiConfig.data);
113-
storedRequests.push(omit({ ...apiConfig, data: newData }, 'onSuccess'));
127+
storedRequests.push(
128+
omit(
129+
{ ...apiConfig, data: newData, type: apiConfig.type },
130+
'onSuccess'
131+
)
132+
);
114133
} else {
115134
console.log('Saving request normally');
116-
storedRequests.push(omit({ ...apiConfig }, 'onSuccess'));
135+
storedRequests.push(
136+
omit({ ...apiConfig, type: apiConfig.type }, 'onSuccess')
137+
);
117138
}
118139
console.log('perform forage after:', { storedRequests });
119140
const result = await localForage.setItem(
@@ -126,30 +147,74 @@ export const OfflineSyncProvider: FC<{
126147
}
127148
};
128149

150+
const saveResponseToOfflineStorage = async (type: string, response: any) => {
151+
try {
152+
const storedResponses: Record<string, any> =
153+
(await localForage.getItem(OFFLINE_RESPONSES_STORAGE_KEY)) || {};
154+
if (!storedResponses[type]) {
155+
storedResponses[type] = [];
156+
}
157+
storedResponses[type].push(response);
158+
await localForage.setItem(OFFLINE_RESPONSES_STORAGE_KEY, storedResponses);
159+
} catch (error) {
160+
console.error('Error saving response to offline storage:', error);
161+
}
162+
};
163+
164+
const getOfflineResponses = async (type: string) => {
165+
try {
166+
const storedResponses: Record<string, any> =
167+
(await localForage.getItem(OFFLINE_RESPONSES_STORAGE_KEY)) || {};
168+
return storedResponses[type] || [];
169+
} catch (error) {
170+
console.error('Error getting offline responses:', error);
171+
return [];
172+
}
173+
};
174+
129175
// Function to perform the actual API request and handle retries
130176
const performRequest = async (config: any): Promise<any> => {
131-
console.log("Inside performRequest")
177+
console.log('Inside performRequest');
132178
try {
179+
let requestData = config.data;
180+
if (typeof requestData === 'function') {
181+
const dependencyResponses = config.dependsOn
182+
? await getOfflineResponses(config.dependsOn)
183+
: [];
184+
requestData = await requestData(dependencyResponses);
185+
}
186+
133187
let response;
134-
if (config?.isFormdata && !(config?.data instanceof FormData)) {
135-
const updateConfig = { ...config, data: objectToFormData(config.data) };
188+
if (config?.isFormdata && !(requestData instanceof FormData)) {
189+
const updateConfig = { ...config, data: objectToFormData(requestData) };
136190
response = await api.request(updateConfig);
137191
} else {
138-
response = await api.request(config);
192+
response = await api.request({ ...config, data: requestData });
193+
}
194+
195+
if (config.type) {
196+
await saveResponseToOfflineStorage(config.type, response.data);
139197
}
140198

141199
onCallback && onCallback({ config, data: response, sendRequest });
142200
return response.data;
143201
} catch (error) {
144202
console.log('packageError', { error });
145-
console.log("Inside performRequest error: ", { rc: config.retryCount, RETRY_LIMIT })
146-
if (config.retryCount < RETRY_LIMIT) {
203+
console.log('Inside performRequest error: ', {
204+
rc: config.retryCount,
205+
RETRY_LIMIT,
206+
});
207+
if ((config.retryCount ?? 0) < RETRY_LIMIT) {
147208
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS));
148-
config.retryCount++;
209+
if (config.retryCount === undefined) {
210+
config.retryCount = 1;
211+
} else {
212+
config.retryCount++;
213+
}
149214
return performRequest(config);
150215
} else {
151216
// Retry limit reached, save the request to offline storage
152-
console.log("Saving request to offline storage");
217+
console.log('Saving request to offline storage');
153218
await saveRequestToOfflineStorage(config);
154219
return error;
155220
// throw new Error('Exceeded retry limit, request saved for offline sync.');
@@ -168,36 +233,109 @@ export const OfflineSyncProvider: FC<{
168233
}
169234
};
170235

236+
const processRequestsSequentially = async (
237+
requests: ApiRequest[],
238+
executionOrder?: string[]
239+
) => {
240+
const results = [];
241+
242+
if (executionOrder && executionOrder.length > 0) {
243+
const requestsByType: Record<string, ApiRequest[]> = {};
244+
245+
for (const request of requests) {
246+
const type = request.type || 'default';
247+
if (!requestsByType[type]) {
248+
requestsByType[type] = [];
249+
}
250+
requestsByType[type].push(request);
251+
}
252+
253+
for (const type of executionOrder) {
254+
const typeRequests = requestsByType[type] || [];
255+
for (const request of typeRequests) {
256+
try {
257+
const result = await performRequest(request);
258+
results.push({ request, result });
259+
} catch (error) {
260+
console.error(`Error processing ${type} request:`, error);
261+
results.push({ request, error });
262+
}
263+
}
264+
}
265+
266+
for (const type in requestsByType) {
267+
if (!executionOrder.includes(type)) {
268+
for (const request of requestsByType[type]) {
269+
try {
270+
const result = await performRequest(request);
271+
results.push({ request, result });
272+
} catch (error) {
273+
console.error(`Error processing ${type} request:`, error);
274+
results.push({ request, error });
275+
}
276+
}
277+
}
278+
}
279+
} else {
280+
for (const request of requests) {
281+
try {
282+
const result = await performRequest(request);
283+
results.push({ request, result });
284+
} catch (error) {
285+
console.error(`Error processing request:`, error);
286+
results.push({ request, error });
287+
}
288+
}
289+
}
290+
291+
return results;
292+
};
293+
171294
const syncOfflineRequests = async () => {
172295
if (isSyncing.current) {
173296
return;
174297
}
175298
isSyncing.current = true;
176299
const storedRequests: any = await getStoredRequests();
177300
if (!storedRequests || storedRequests.length === 0) {
301+
isSyncing.current = false;
178302
return;
179303
}
180304

181-
console.log("Inside syncOfflineRequests", storedRequests)
305+
console.log('Inside syncOfflineRequests', storedRequests);
182306
const requestClone = [...storedRequests];
183-
for (const request of storedRequests) {
184-
console.log("Inside syncOfflineRequests loop, ", storedRequests)
185-
if (request) {
186-
try {
187-
await performRequest(request);
188-
// Remove the request with a matching id from requestClone
307+
308+
try {
309+
let results;
310+
if (config?.executionOrder) {
311+
results = await processRequestsSequentially(
312+
requestClone,
313+
config.executionOrder
314+
);
315+
} else if (config?.sequentialProcessing) {
316+
results = await processRequestsSequentially(requestClone);
317+
} else {
318+
results = await Promise.all(requestClone.map(performRequest));
319+
}
320+
321+
for (const result of results) {
322+
const request = result.request || result;
323+
const error = result.error;
324+
if (!error) {
189325
const updatedRequests = requestClone.filter(
190326
sr => sr.id !== request.id
191327
);
192328
requestClone.splice(0, requestClone.length, ...updatedRequests);
193-
} catch (error) {
194-
console.log({ error });
195-
} finally {
196-
await localForage.setItem(API_REQUESTS_STORAGE_KEY, requestClone);
329+
} else {
330+
console.error('Failed to process request:', request, error);
197331
}
198332
}
333+
} catch (error) {
334+
console.error('Error in syncOfflineRequests:', error);
335+
} finally {
336+
await localForage.setItem(API_REQUESTS_STORAGE_KEY, requestClone);
337+
isSyncing.current = false;
199338
}
200-
isSyncing.current = false;
201339
};
202340

203341
return (

0 commit comments

Comments
 (0)