1
+ import { openDB } from 'idb' ;
2
+
3
+ const dbPromise = openDB ( 'restful-queue' , 1 , {
4
+ upgrade ( db ) {
5
+ db . createObjectStore ( 'requests' , {
6
+ keyPath : 'id' ,
7
+ autoIncrement : true ,
8
+ } ) ;
9
+ } ,
10
+ } ) ;
11
+
12
+ /**
13
+ * Stores a request in the queue and then attempts to send it to the server
14
+ *
15
+ * @param {Request } request A Request object from the Fetch API, which isn't unusable
16
+ *
17
+ * @returns {Promise }
18
+ */
19
+ export async function queueAndAttemptRequest ( request ) {
20
+ const requestid = await queueRequest ( request ) ;
21
+ syncRequests ( ) ;
22
+ return new Response ( new Blob ( ) , { status : 202 , statusText : "Added to Queue" } ) ;
23
+ }
24
+
25
+ /**
26
+ * Stores a request object in indexDB
27
+ *
28
+ * @param {Request } request A Request object from the Fetch API
29
+ *
30
+ * @returns {Promise.<number> } A promise which resolves with a unique requestid when succesfully stored (or rejects on failure)
31
+ */
32
+ async function queueRequest ( request ) {
33
+
34
+ // Store a cloned version of the request, so the orginal can still be fetched later
35
+ request = request . clone ( ) ;
36
+ const { url, method } = request ;
37
+ const headers = [ ...request . headers ] ;
38
+ const body = await request . blob ( ) ;
39
+ const rawData = { url, method, headers, body } ;
40
+
41
+ const db = await dbPromise ;
42
+ const requestid = await db . add ( 'requests' , rawData ) ;
43
+ return requestid ;
44
+ }
45
+
46
+ /**
47
+ * Attempts to fetch a request from the queue. If successful, the request is removed from the queue.
48
+ *
49
+ * @param {number } requestid The unique ID for this request stored in indexDB
50
+ * @param {Request } request A Request object from the Fetch API
51
+ *
52
+ * @returns {Promise.<Response> } A promise which resolves with the requests response following removal from the queue (or rejects on failure)
53
+ */
54
+ async function attemptRequest ( requestid , request ) {
55
+ const response = await fetch ( request ) ;
56
+ await removeFromQueue ( requestid ) ;
57
+ return response ;
58
+ }
59
+
60
+ /**
61
+ * Removes a request from the queue
62
+ *
63
+ * @param {number } requestid The unique ID for the request to remove from indexDB+
64
+ *
65
+ * @returns {Promise } A promise which resolves when succesfully removed (or rejects on failure)
66
+ */
67
+ async function removeFromQueue ( requestid ) {
68
+ const db = await dbPromise ;
69
+ await db . delete ( 'requests' , requestid ) ;
70
+ }
71
+
72
+ /**
73
+ * Fetches all the outstanding requests, along with their IDs from indexDB
74
+ * NB: getOutstandRequests is a simplified public wrapper for this function, which doesn't expose the internal requestids
75
+ *
76
+ * @returns {Array.<{id: number, request: Request}> } An array containing requests and their associated requestids
77
+ */
78
+ async function getOutstandingRequestsAndIds ( ) {
79
+ const db = await dbPromise ;
80
+ return ( await db . getAll ( 'requests' ) ) . map ( raw => {
81
+ const { url, method, headers, body, id } = raw ;
82
+ const request = new Request ( url , { method, headers, body} ) ;
83
+ return { id, request} ;
84
+ } ) ;
85
+ }
86
+
87
+ /**
88
+ * Fetches all the outstanding requests from indexDB
89
+ *
90
+ * @returns {Array.<Request> } An array containing Fetch API Request objects
91
+ */
92
+ export async function getOutstandingRequests ( ) {
93
+ return ( await getOutstandingRequestsAndIds ( ) )
94
+ . map ( ( { request} ) => request ) ;
95
+ }
96
+
97
+ let currentSync = null ;
98
+
99
+ /**
100
+ * Starts off an asynchronous function to sync up any outstanding requests with the server
101
+ * Ensures there's only one running at a time to avoid race conditions
102
+ */
103
+ export function syncRequests ( ) {
104
+
105
+ // If there's no existing sync, then start one
106
+ if ( ! currentSync ) {
107
+ currentSync = attemptOutstandingRequests ( ) ;
108
+ return ;
109
+ }
110
+
111
+ // Otherwise, queue another sync after the current one.
112
+ currentSync = currentSync . then ( attemptOutstandingRequests ) ;
113
+
114
+ }
115
+
116
+ /**
117
+ * Attempts to fetch an outstanding requests, and if successful remove them from the queue.
118
+ * Stops after the first failure and doesn't attempt any subsequent requests in the queue.
119
+ * NB: Calling this function whilst a previous invocation hasn't completed yet, may cause a race condition. Use the `syncRequests` function to avoid this.
120
+ *
121
+ * @returns {Promise } A promise which resolves when all requests have been succesfully removed from the queue, or rejects after encountering the first failure
122
+ */
123
+ async function attemptOutstandingRequests ( ) {
124
+ const requests = await getOutstandingRequestsAndIds ( ) ;
125
+ for ( const { id, request} of requests ) {
126
+ await attemptRequest ( id , request ) ;
127
+ }
128
+ }
0 commit comments