@@ -6,17 +6,18 @@ import React, {
6
6
useContext ,
7
7
useEffect ,
8
8
useState ,
9
- useRef
9
+ useRef ,
10
10
} from 'react' ;
11
11
import { DataSyncContext } from './data-sync-context' ;
12
12
import axios from 'axios' ;
13
13
import localForage from 'localforage' ;
14
-
15
14
import { omit } from 'underscore' ;
15
+
16
16
const api = axios . create ( ) ;
17
17
const RETRY_LIMIT = 0 ;
18
18
const RETRY_DELAY_MS = 1000 ;
19
19
const API_REQUESTS_STORAGE_KEY = 'apiRequests' ;
20
+ const OFFLINE_RESPONSES_STORAGE_KEY = 'offlineResponses' ;
20
21
21
22
import {
22
23
generateUuid ,
@@ -32,21 +33,35 @@ import useNetworkStatus from './hooks/useNetworkStatus';
32
33
// const hasWindow = () => {
33
34
// return window && typeof window !== 'undefined';
34
35
// };
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
+
35
47
type ConfigType = {
36
48
isFormdata ?: boolean ;
37
49
maxRetry ?: number ;
50
+ executionOrder ?: string [ ] ;
51
+ sequentialProcessing ?: boolean ;
38
52
} ;
53
+
39
54
export const OfflineSyncProvider : FC < {
40
55
children : ReactElement ;
41
56
render ?: ( status : { isOffline ?: boolean ; isOnline : boolean } ) => ReactNode ;
42
57
onStatusChange ?: ( status : { isOnline : boolean } ) => void ;
43
58
onCallback ?: ( data : any ) => void ;
44
59
toastConfig ?: any ;
45
60
config ?: ConfigType ;
46
- } > = ( { children, render, onStatusChange, onCallback } ) => {
61
+ } > = ( { children, render, onStatusChange, onCallback, config } ) => {
47
62
// Manage state for data, offline status, and online status
48
63
const [ data , setData ] = useState < Record < string , any > > ( { } ) ;
49
- const isSyncing = useRef < boolean > ( ) ;
64
+ const isSyncing = useRef < boolean > ( false ) ;
50
65
const [ isOnline , setIsOnline ] = useState < boolean > (
51
66
window ?. navigator ?. onLine ?? true
52
67
) ;
@@ -61,7 +76,6 @@ export const OfflineSyncProvider: FC<{
61
76
handleOnline ( ) ;
62
77
} else {
63
78
handleOffline ( ) ;
64
-
65
79
}
66
80
}
67
81
} , [ isConnected ] ) ;
@@ -101,7 +115,7 @@ export const OfflineSyncProvider: FC<{
101
115
102
116
const saveRequestToOfflineStorage = async ( apiConfig : any ) => {
103
117
try {
104
- const storedRequests : Array < any > =
118
+ const storedRequests : any =
105
119
( await localForage . getItem ( API_REQUESTS_STORAGE_KEY ) ) || [ ] ;
106
120
console . log ( 'perform stored' , {
107
121
req : storedRequests ,
@@ -110,10 +124,17 @@ export const OfflineSyncProvider: FC<{
110
124
if ( apiConfig ?. isFormdata && apiConfig ?. data instanceof FormData ) {
111
125
// console.log({ apiConfig })
112
126
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
+ ) ;
114
133
} else {
115
134
console . log ( 'Saving request normally' ) ;
116
- storedRequests . push ( omit ( { ...apiConfig } , 'onSuccess' ) ) ;
135
+ storedRequests . push (
136
+ omit ( { ...apiConfig , type : apiConfig . type } , 'onSuccess' )
137
+ ) ;
117
138
}
118
139
console . log ( 'perform forage after:' , { storedRequests } ) ;
119
140
const result = await localForage . setItem (
@@ -126,30 +147,74 @@ export const OfflineSyncProvider: FC<{
126
147
}
127
148
} ;
128
149
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
+
129
175
// Function to perform the actual API request and handle retries
130
176
const performRequest = async ( config : any ) : Promise < any > => {
131
- console . log ( " Inside performRequest" )
177
+ console . log ( ' Inside performRequest' ) ;
132
178
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
+
133
187
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 ) } ;
136
190
response = await api . request ( updateConfig ) ;
137
191
} 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 ) ;
139
197
}
140
198
141
199
onCallback && onCallback ( { config, data : response , sendRequest } ) ;
142
200
return response . data ;
143
201
} catch ( error ) {
144
202
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 ) {
147
208
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
+ }
149
214
return performRequest ( config ) ;
150
215
} else {
151
216
// 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' ) ;
153
218
await saveRequestToOfflineStorage ( config ) ;
154
219
return error ;
155
220
// throw new Error('Exceeded retry limit, request saved for offline sync.');
@@ -168,36 +233,109 @@ export const OfflineSyncProvider: FC<{
168
233
}
169
234
} ;
170
235
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
+
171
294
const syncOfflineRequests = async ( ) => {
172
295
if ( isSyncing . current ) {
173
296
return ;
174
297
}
175
298
isSyncing . current = true ;
176
299
const storedRequests : any = await getStoredRequests ( ) ;
177
300
if ( ! storedRequests || storedRequests . length === 0 ) {
301
+ isSyncing . current = false ;
178
302
return ;
179
303
}
180
304
181
- console . log ( " Inside syncOfflineRequests" , storedRequests )
305
+ console . log ( ' Inside syncOfflineRequests' , storedRequests ) ;
182
306
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 ) {
189
325
const updatedRequests = requestClone . filter (
190
326
sr => sr . id !== request . id
191
327
) ;
192
328
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 ) ;
197
331
}
198
332
}
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 ;
199
338
}
200
- isSyncing . current = false ;
201
339
} ;
202
340
203
341
return (
0 commit comments