Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/src/Fetch.js
4128 views
1
/**
2
* @license
3
* Copyright 2016 The Emscripten Authors
4
* SPDX-License-Identifier: MIT
5
*/
6
7
var Fetch = {
8
// HandleAllocator for XHR request object
9
// xhrs: undefined,
10
11
// The web worker that runs proxied file I/O requests. (this field is
12
// populated on demand, start as undefined to save code size)
13
// worker: undefined,
14
15
// Specifies an instance to the IndexedDB database. The database is opened
16
// as a preload step before the Emscripten application starts. (this field is
17
// populated on demand, start as undefined to save code size)
18
// dbInstance: undefined,
19
20
#if FETCH_SUPPORT_INDEXEDDB
21
async openDatabase(dbname, dbversion) {
22
return new Promise((resolve, reject) => {
23
try {
24
#if FETCH_DEBUG
25
dbg(`fetch: indexedDB.open(dbname="${dbname}", dbversion="${dbversion}");`);
26
#endif
27
var openRequest = indexedDB.open(dbname, dbversion);
28
} catch (e) {
29
return reject(e);
30
}
31
32
openRequest.onupgradeneeded = (event) => {
33
#if FETCH_DEBUG
34
dbg('fetch: IndexedDB upgrade needed. Clearing database.');
35
#endif
36
var db = /** @type {IDBDatabase} */ (event.target.result);
37
if (db.objectStoreNames.contains('FILES')) {
38
db.deleteObjectStore('FILES');
39
}
40
db.createObjectStore('FILES');
41
};
42
openRequest.onsuccess = (event) => resolve(event.target.result);
43
openRequest.onerror = reject;
44
});
45
},
46
#endif
47
48
async init() {
49
Fetch.xhrs = new HandleAllocator();
50
#if FETCH_SUPPORT_INDEXEDDB
51
#if PTHREADS
52
if (ENVIRONMENT_IS_PTHREAD) return;
53
#endif
54
55
addRunDependency('library_fetch_init');
56
try {
57
var db = await Fetch.openDatabase('emscripten_filesystem', 1);
58
#if FETCH_DEBUG
59
dbg('fetch: IndexedDB successfully opened.');
60
#endif
61
Fetch.dbInstance = db;
62
} catch (e) {
63
#if FETCH_DEBUG
64
dbg('fetch: IndexedDB open failed.');
65
#endif
66
Fetch.dbInstance = false;
67
} finally {
68
removeRunDependency('library_fetch_init');
69
}
70
#endif // ~FETCH_SUPPORT_INDEXEDDB
71
}
72
}
73
74
#if FETCH_SUPPORT_INDEXEDDB
75
function fetchDeleteCachedData(db, fetch, onsuccess, onerror) {
76
if (!db) {
77
#if FETCH_DEBUG
78
dbg('fetch: IndexedDB not available!');
79
#endif
80
onerror(fetch, 0, 'IndexedDB not available!');
81
return;
82
}
83
84
var fetch_attr = fetch + {{{ C_STRUCTS.emscripten_fetch_t.__attributes }}};
85
var path = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.destinationPath, '*') }}};
86
path ||= {{{ makeGetValue('fetch', C_STRUCTS.emscripten_fetch_t.url, '*') }}};
87
88
var pathStr = UTF8ToString(path);
89
90
try {
91
var transaction = db.transaction(['FILES'], 'readwrite');
92
var packages = transaction.objectStore('FILES');
93
var request = packages.delete(pathStr);
94
request.onsuccess = (event) => {
95
var value = event.target.result;
96
#if FETCH_DEBUG
97
dbg(`fetch: Deleted file ${pathStr} from IndexedDB`);
98
#endif
99
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.data, 0, '*') }}};
100
writeI53ToI64(fetch + {{{ C_STRUCTS.emscripten_fetch_t.numBytes }}}, 0);
101
writeI53ToI64(fetch + {{{ C_STRUCTS.emscripten_fetch_t.dataOffset }}}, 0);
102
writeI53ToI64(fetch + {{{ C_STRUCTS.emscripten_fetch_t.totalBytes }}}, 0);
103
// Mimic XHR readyState 4 === 'DONE: The operation is complete'
104
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.readyState, 4, 'i16') }}};
105
// Mimic XHR HTTP status code 200 "OK"
106
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.status, 200, 'i16') }}};
107
stringToUTF8("OK", fetch + {{{ C_STRUCTS.emscripten_fetch_t.statusText }}}, 64);
108
onsuccess(fetch, 0, value);
109
};
110
request.onerror = (error) => {
111
#if FETCH_DEBUG
112
dbg(`fetch: Failed to delete file ${pathStr} from IndexedDB! error: ${error}`);
113
#endif
114
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.readyState, 4, 'i16') }}} // Mimic XHR readyState 4 === 'DONE: The operation is complete'
115
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.status, 404, 'i16') }}} // Mimic XHR HTTP status code 404 "Not Found"
116
stringToUTF8("Not Found", fetch + {{{ C_STRUCTS.emscripten_fetch_t.statusText }}}, 64);
117
onerror(fetch, 0, error);
118
};
119
} catch(e) {
120
#if FETCH_DEBUG
121
dbg(`fetch: Failed to load file ${pathStr} from IndexedDB! Got exception ${e}`);
122
#endif
123
onerror(fetch, 0, e);
124
}
125
}
126
127
function fetchLoadCachedData(db, fetch, onsuccess, onerror) {
128
if (!db) {
129
#if FETCH_DEBUG
130
dbg('fetch: IndexedDB not available!');
131
#endif
132
onerror(fetch, 0, 'IndexedDB not available!');
133
return;
134
}
135
136
var fetch_attr = fetch + {{{ C_STRUCTS.emscripten_fetch_t.__attributes }}};
137
var path = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.destinationPath, '*') }}};
138
path ||= {{{ makeGetValue('fetch', C_STRUCTS.emscripten_fetch_t.url, '*') }}};
139
var pathStr = UTF8ToString(path);
140
141
try {
142
var transaction = db.transaction(['FILES'], 'readonly');
143
var packages = transaction.objectStore('FILES');
144
var getRequest = packages.get(pathStr);
145
getRequest.onsuccess = (event) => {
146
if (event.target.result) {
147
var value = event.target.result;
148
var len = value.byteLength || value.length;
149
#if FETCH_DEBUG
150
dbg(`fetch: Loaded file ${pathStr} from IndexedDB, length: ${len}`);
151
#endif
152
// The data pointer malloc()ed here has the same lifetime as the emscripten_fetch_t structure itself has, and is
153
// freed when emscripten_fetch_close() is called.
154
var ptr = _malloc(len);
155
HEAPU8.set(new Uint8Array(value), ptr);
156
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.data, 'ptr', '*') }}};
157
writeI53ToI64(fetch + {{{ C_STRUCTS.emscripten_fetch_t.numBytes }}}, len);
158
writeI53ToI64(fetch + {{{ C_STRUCTS.emscripten_fetch_t.dataOffset }}}, 0);
159
writeI53ToI64(fetch + {{{ C_STRUCTS.emscripten_fetch_t.totalBytes }}}, len);
160
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.readyState, 4, 'i16') }}} // Mimic XHR readyState 4 === 'DONE: The operation is complete'
161
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.status, 200, 'i16') }}} // Mimic XHR HTTP status code 200 "OK"
162
stringToUTF8("OK", fetch + {{{ C_STRUCTS.emscripten_fetch_t.statusText }}}, 64);
163
onsuccess(fetch, 0, value);
164
} else {
165
// Succeeded to load, but the load came back with the value of undefined, treat that as an error since we never store undefined in db.
166
#if FETCH_DEBUG
167
dbg(`fetch: File ${pathStr} not found in IndexedDB`);
168
#endif
169
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.readyState, 4, 'i16') }}} // Mimic XHR readyState 4 === 'DONE: The operation is complete'
170
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.status, 404, 'i16') }}} // Mimic XHR HTTP status code 404 "Not Found"
171
stringToUTF8("Not Found", fetch + {{{ C_STRUCTS.emscripten_fetch_t.statusText }}}, 64);
172
onerror(fetch, 0, 'no data');
173
}
174
};
175
getRequest.onerror = (error) => {
176
#if FETCH_DEBUG
177
dbg(`fetch: Failed to load file ${pathStr} from IndexedDB!`);
178
#endif
179
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.readyState, 4, 'i16') }}} // Mimic XHR readyState 4 === 'DONE: The operation is complete'
180
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.status, 404, 'i16') }}} // Mimic XHR HTTP status code 404 "Not Found"
181
stringToUTF8("Not Found", fetch + {{{ C_STRUCTS.emscripten_fetch_t.statusText }}}, 64);
182
onerror(fetch, 0, error);
183
};
184
} catch(e) {
185
#if FETCH_DEBUG
186
dbg(`fetch: Failed to load file ${pathStr} from IndexedDB! Got exception ${e}`);
187
#endif
188
onerror(fetch, 0, e);
189
}
190
}
191
192
function fetchCacheData(/** @type {IDBDatabase} */ db, fetch, data, onsuccess, onerror) {
193
if (!db) {
194
#if FETCH_DEBUG
195
dbg('fetch: IndexedDB not available!');
196
#endif
197
onerror(fetch, 0, 'IndexedDB not available!');
198
return;
199
}
200
201
var fetch_attr = fetch + {{{ C_STRUCTS.emscripten_fetch_t.__attributes }}};
202
var destinationPath = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.destinationPath, '*') }}};
203
destinationPath ||= {{{ makeGetValue('fetch', C_STRUCTS.emscripten_fetch_t.url, '*') }}};
204
var destinationPathStr = UTF8ToString(destinationPath);
205
206
try {
207
var transaction = db.transaction(['FILES'], 'readwrite');
208
var packages = transaction.objectStore('FILES');
209
var putRequest = packages.put(data, destinationPathStr);
210
putRequest.onsuccess = (event) => {
211
#if FETCH_DEBUG
212
dbg(`fetch: Stored file "${destinationPathStr}" to IndexedDB cache.`);
213
#endif
214
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.readyState, 4, 'i16') }}} // Mimic XHR readyState 4 === 'DONE: The operation is complete'
215
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.status, 200, 'i16') }}} // Mimic XHR HTTP status code 200 "OK"
216
stringToUTF8("OK", fetch + {{{ C_STRUCTS.emscripten_fetch_t.statusText }}}, 64);
217
onsuccess(fetch, 0, destinationPathStr);
218
};
219
putRequest.onerror = (error) => {
220
#if FETCH_DEBUG
221
dbg(`fetch: Failed to store file "${destinationPathStr}" to IndexedDB cache!`);
222
#endif
223
// Most likely we got an error if IndexedDB is unwilling to store any more data for this page.
224
// TODO: Can we identify and break down different IndexedDB-provided errors and convert those
225
// to more HTTP status codes for more information?
226
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.readyState, 4, 'i16') }}} // Mimic XHR readyState 4 === 'DONE: The operation is complete'
227
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.status, 413, 'i16') }}} // Mimic XHR HTTP status code 413 "Payload Too Large"
228
stringToUTF8("Payload Too Large", fetch + {{{ C_STRUCTS.emscripten_fetch_t.statusText }}}, 64);
229
onerror(fetch, 0, error);
230
};
231
} catch(e) {
232
#if FETCH_DEBUG
233
dbg(`fetch: Failed to store file "${destinationPathStr}" to IndexedDB cache! Exception: ${e}`);
234
#endif
235
onerror(fetch, 0, e);
236
}
237
}
238
#endif // ~FETCH_SUPPORT_INDEXEDDB
239
240
function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) {
241
var url = {{{ makeGetValue('fetch', C_STRUCTS.emscripten_fetch_t.url, '*') }}};
242
if (!url) {
243
#if FETCH_DEBUG
244
dbg('fetch: XHR failed, no URL specified!');
245
#endif
246
onerror(fetch, 'no url specified!');
247
return;
248
}
249
var url_ = UTF8ToString(url);
250
251
var fetch_attr = fetch + {{{ C_STRUCTS.emscripten_fetch_t.__attributes }}};
252
var requestMethod = UTF8ToString(fetch_attr + {{{ C_STRUCTS.emscripten_fetch_attr_t.requestMethod }}});
253
requestMethod ||= 'GET';
254
var timeoutMsecs = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.timeoutMSecs, 'u32') }}};
255
var userName = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.userName, '*') }}};
256
var password = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.password, '*') }}};
257
var requestHeaders = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.requestHeaders, '*') }}};
258
var overriddenMimeType = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.overriddenMimeType, '*') }}};
259
var dataPtr = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.requestData, '*') }}};
260
var dataLength = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.requestDataSize, '*') }}};
261
262
var fetchAttributes = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.attributes, 'u32') }}};
263
var fetchAttrLoadToMemory = !!(fetchAttributes & {{{ cDefs.EMSCRIPTEN_FETCH_LOAD_TO_MEMORY }}});
264
var fetchAttrStreamData = !!(fetchAttributes & {{{ cDefs.EMSCRIPTEN_FETCH_STREAM_DATA }}});
265
var fetchAttrSynchronous = !!(fetchAttributes & {{{ cDefs.EMSCRIPTEN_FETCH_SYNCHRONOUS }}});
266
267
var userNameStr = userName ? UTF8ToString(userName) : undefined;
268
var passwordStr = password ? UTF8ToString(password) : undefined;
269
270
var xhr = new XMLHttpRequest();
271
xhr.withCredentials = !!{{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.withCredentials, 'u8') }}};;
272
#if FETCH_DEBUG
273
dbg(`fetch: xhr.timeout: ${xhr.timeout}, xhr.withCredentials: ${xhr.withCredentials}`);
274
dbg(`fetch: xhr.open(requestMethod="${requestMethod}", url: "${url}", userName: ${userNameStr}, password: ${passwordStr}`);
275
#endif
276
xhr.open(requestMethod, url_, !fetchAttrSynchronous, userNameStr, passwordStr);
277
if (!fetchAttrSynchronous) xhr.timeout = timeoutMsecs; // XHR timeout field is only accessible in async XHRs, and must be set after .open() but before .send().
278
xhr.url_ = url_; // Save the url for debugging purposes (and for comparing to the responseURL that server side advertised)
279
#if ASSERTIONS
280
assert(!fetchAttrStreamData, 'streaming uses moz-chunked-arraybuffer which is no longer supported; TODO: rewrite using fetch()');
281
#endif
282
xhr.responseType = 'arraybuffer';
283
284
if (overriddenMimeType) {
285
var overriddenMimeTypeStr = UTF8ToString(overriddenMimeType);
286
#if FETCH_DEBUG
287
dbg(`fetch: xhr.overrideMimeType("${overriddenMimeTypeStr}");`);
288
#endif
289
xhr.overrideMimeType(overriddenMimeTypeStr);
290
}
291
if (requestHeaders) {
292
for (;;) {
293
var key = {{{ makeGetValue('requestHeaders', 0, '*') }}};
294
if (!key) break;
295
var value = {{{ makeGetValue('requestHeaders', POINTER_SIZE, '*') }}};
296
if (!value) break;
297
requestHeaders += {{{ 2 * POINTER_SIZE }}};
298
var keyStr = UTF8ToString(key);
299
var valueStr = UTF8ToString(value);
300
#if FETCH_DEBUG
301
dbg(`fetch: xhr.setRequestHeader("${keyStr}", "${valueStr}");`);
302
#endif
303
xhr.setRequestHeader(keyStr, valueStr);
304
}
305
}
306
307
var id = Fetch.xhrs.allocate(xhr);
308
#if FETCH_DEBUG
309
dbg(`fetch: id=${id}`);
310
#endif
311
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.id, 'id', 'u32') }}};
312
var data = (dataPtr && dataLength) ? HEAPU8.slice(dataPtr, dataPtr + dataLength) : null;
313
// TODO: Support specifying custom headers to the request.
314
315
// Share the code to save the response, as we need to do so both on success
316
// and on error (despite an error, there may be a response, like a 404 page).
317
// This receives a condition, which determines whether to save the xhr's
318
// response, or just 0.
319
function saveResponseAndStatus() {
320
var ptr = 0;
321
var ptrLen = 0;
322
if (xhr.response && fetchAttrLoadToMemory && {{{ makeGetValue('fetch', C_STRUCTS.emscripten_fetch_t.data, '*') }}} === 0) {
323
ptrLen = xhr.response.byteLength;
324
}
325
if (ptrLen > 0) {
326
#if FETCH_DEBUG
327
dbg(`fetch: allocating ${ptrLen} bytes in Emscripten heap for xhr data`);
328
#endif
329
// The data pointer malloc()ed here has the same lifetime as the emscripten_fetch_t structure itself has, and is
330
// freed when emscripten_fetch_close() is called.
331
ptr = _malloc(ptrLen);
332
HEAPU8.set(new Uint8Array(/** @type{Array<number>} */(xhr.response)), ptr);
333
}
334
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.data, 'ptr', '*') }}}
335
writeI53ToI64(fetch + {{{ C_STRUCTS.emscripten_fetch_t.numBytes }}}, ptrLen);
336
writeI53ToI64(fetch + {{{ C_STRUCTS.emscripten_fetch_t.dataOffset }}}, 0);
337
var len = xhr.response ? xhr.response.byteLength : 0;
338
if (len) {
339
// If the final XHR.onload handler receives the bytedata to compute total length, report that,
340
// otherwise don't write anything out here, which will retain the latest byte size reported in
341
// the most recent XHR.onprogress handler.
342
writeI53ToI64(fetch + {{{ C_STRUCTS.emscripten_fetch_t.totalBytes }}}, len);
343
}
344
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.readyState, 'xhr.readyState', 'i16') }}}
345
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.status, 'xhr.status', 'i16') }}}
346
if (xhr.statusText) stringToUTF8(xhr.statusText, fetch + {{{ C_STRUCTS.emscripten_fetch_t.statusText }}}, 64);
347
if (fetchAttrSynchronous) {
348
// The response url pointer malloc()ed here has the same lifetime as the emscripten_fetch_t structure itself has, and is
349
// freed when emscripten_fetch_close() is called.
350
var ruPtr = stringToNewUTF8(xhr.responseURL);
351
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.responseUrl, 'ruPtr', '*') }}}
352
}
353
}
354
355
xhr.onload = (e) => {
356
// check if xhr was aborted by user and don't try to call back
357
if (!Fetch.xhrs.has(id)) {
358
return;
359
}
360
saveResponseAndStatus();
361
if (xhr.status >= 200 && xhr.status < 300) {
362
#if FETCH_DEBUG
363
dbg(`fetch: xhr of URL "${xhr.url_}" / responseURL "${xhr.responseURL}" succeeded with status ${xhr.status}`);
364
#endif
365
onsuccess(fetch, xhr, e);
366
} else {
367
#if FETCH_DEBUG
368
dbg(`fetch: xhr of URL "${xhr.url_}" / responseURL "${xhr.responseURL}" failed with status ${xhr.status}`);
369
#endif
370
onerror(fetch, e);
371
}
372
};
373
xhr.onerror = (e) => {
374
// check if xhr was aborted by user and don't try to call back
375
if (!Fetch.xhrs.has(id)) {
376
return;
377
}
378
#if FETCH_DEBUG
379
dbg(`fetch: xhr of URL "${xhr.url_}" / responseURL "${xhr.responseURL}" finished with error, readyState ${xhr.readyState} and status ${xhr.status}`);
380
#endif
381
saveResponseAndStatus();
382
onerror(fetch, e);
383
};
384
xhr.ontimeout = (e) => {
385
// check if xhr was aborted by user and don't try to call back
386
if (!Fetch.xhrs.has(id)) {
387
return;
388
}
389
#if FETCH_DEBUG
390
dbg(`fetch: xhr of URL "${xhr.url_}" / responseURL "${xhr.responseURL}" timed out, readyState ${xhr.readyState} and status ${xhr.status}`);
391
#endif
392
onerror(fetch, e);
393
};
394
xhr.onprogress = (e) => {
395
// check if xhr was aborted by user and don't try to call back
396
if (!Fetch.xhrs.has(id)) {
397
return;
398
}
399
var ptrLen = (fetchAttrLoadToMemory && fetchAttrStreamData && xhr.response) ? xhr.response.byteLength : 0;
400
var ptr = 0;
401
if (ptrLen > 0 && fetchAttrLoadToMemory && fetchAttrStreamData) {
402
#if FETCH_DEBUG
403
dbg(`fetch: allocating ${ptrLen} bytes in Emscripten heap for xhr data`);
404
#endif
405
#if ASSERTIONS
406
assert(onprogress, 'When doing a streaming fetch, you should have an onprogress handler registered to receive the chunks!');
407
#endif
408
// Allocate byte data in Emscripten heap for the streamed memory block (freed immediately after onprogress call)
409
ptr = _malloc(ptrLen);
410
HEAPU8.set(new Uint8Array(/** @type{Array<number>} */(xhr.response)), ptr);
411
}
412
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.data, 'ptr', '*') }}}
413
writeI53ToI64(fetch + {{{ C_STRUCTS.emscripten_fetch_t.numBytes }}}, ptrLen);
414
writeI53ToI64(fetch + {{{ C_STRUCTS.emscripten_fetch_t.dataOffset }}}, e.loaded - ptrLen);
415
writeI53ToI64(fetch + {{{ C_STRUCTS.emscripten_fetch_t.totalBytes }}}, e.total);
416
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.readyState, 'xhr.readyState', 'i16') }}}
417
var status = xhr.status;
418
// If loading files from a source that does not give HTTP status code, assume success if we get data bytes
419
if (xhr.readyState >= 3 && xhr.status === 0 && e.loaded > 0) status = 200;
420
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.status, 'status', 'i16') }}}
421
if (xhr.statusText) stringToUTF8(xhr.statusText, fetch + {{{ C_STRUCTS.emscripten_fetch_t.statusText }}}, 64);
422
onprogress(fetch, e);
423
_free(ptr);
424
};
425
xhr.onreadystatechange = (e) => {
426
// check if xhr was aborted by user and don't try to call back
427
if (!Fetch.xhrs.has(id)) {
428
{{{ runtimeKeepalivePop() }}}
429
return;
430
}
431
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.readyState, 'xhr.readyState', 'i16') }}}
432
if (xhr.readyState >= 2) {
433
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.status, 'xhr.status', 'i16') }}}
434
}
435
if (!fetchAttrSynchronous && (xhr.readyState === 2 && xhr.responseURL.length > 0)) {
436
// The response url pointer malloc()ed here has the same lifetime as the emscripten_fetch_t structure itself has, and is
437
// freed when emscripten_fetch_close() is called.
438
var ruPtr = stringToNewUTF8(xhr.responseURL);
439
{{{ makeSetValue('fetch', C_STRUCTS.emscripten_fetch_t.responseUrl, 'ruPtr', '*') }}}
440
}
441
onreadystatechange(fetch, e);
442
};
443
#if FETCH_DEBUG
444
dbg(`fetch: xhr.send(data=${data})`);
445
#endif
446
try {
447
xhr.send(data);
448
} catch(e) {
449
#if FETCH_DEBUG
450
dbg(`fetch: xhr failed with exception: ${e}`);
451
#endif
452
onerror(fetch, e);
453
}
454
}
455
456
function startFetch(fetch, successcb, errorcb, progresscb, readystatechangecb) {
457
// Avoid shutting down the runtime since we want to wait for the async
458
// response.
459
{{{ runtimeKeepalivePush() }}}
460
461
var fetch_attr = fetch + {{{ C_STRUCTS.emscripten_fetch_t.__attributes }}};
462
var onsuccess = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.onsuccess, '*') }}};
463
var onerror = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.onerror, '*') }}};
464
var onprogress = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.onprogress, '*') }}};
465
var onreadystatechange = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.onreadystatechange, '*') }}};
466
var fetchAttributes = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.attributes, '*') }}};
467
var fetchAttrSynchronous = !!(fetchAttributes & {{{ cDefs.EMSCRIPTEN_FETCH_SYNCHRONOUS }}});
468
469
function doCallback(f) {
470
if (fetchAttrSynchronous) {
471
f();
472
} else {
473
callUserCallback(f);
474
}
475
}
476
477
var reportSuccess = (fetch, xhr, e) => {
478
#if FETCH_DEBUG
479
dbg(`fetch: operation success. e: ${e}`);
480
#endif
481
{{{ runtimeKeepalivePop() }}}
482
doCallback(() => {
483
if (onsuccess) {{{ makeDynCall('vp', 'onsuccess') }}}(fetch);
484
else successcb?.(fetch);
485
});
486
};
487
488
var reportProgress = (fetch, e) => {
489
doCallback(() => {
490
if (onprogress) {{{ makeDynCall('vp', 'onprogress') }}}(fetch);
491
else progresscb?.(fetch);
492
});
493
};
494
495
var reportError = (fetch, e) => {
496
#if FETCH_DEBUG
497
dbg(`fetch: operation failed: ${e}`);
498
#endif
499
{{{ runtimeKeepalivePop() }}}
500
doCallback(() => {
501
if (onerror) {{{ makeDynCall('vp', 'onerror') }}}(fetch);
502
else errorcb?.(fetch);
503
});
504
};
505
506
var reportReadyStateChange = (fetch, e) => {
507
#if FETCH_DEBUG
508
dbg(`fetch: ready state change. e: ${e}`);
509
#endif
510
doCallback(() => {
511
if (onreadystatechange) {{{ makeDynCall('vp', 'onreadystatechange') }}}(fetch);
512
else readystatechangecb?.(fetch);
513
});
514
};
515
516
var performUncachedXhr = (fetch, xhr, e) => {
517
#if FETCH_DEBUG
518
dbg(`fetch: starting (uncached) XHR: ${e}`);
519
#endif
520
fetchXHR(fetch, reportSuccess, reportError, reportProgress, reportReadyStateChange);
521
};
522
523
#if FETCH_SUPPORT_INDEXEDDB
524
var cacheResultAndReportSuccess = (fetch, xhr, e) => {
525
#if FETCH_DEBUG
526
dbg(`fetch: operation success. Caching result.. e: ${e}`);
527
#endif
528
var storeSuccess = (fetch, xhr, e) => {
529
#if FETCH_DEBUG
530
dbg('fetch: IndexedDB store succeeded.');
531
#endif
532
{{{ runtimeKeepalivePop() }}}
533
doCallback(() => {
534
if (onsuccess) {{{ makeDynCall('vp', 'onsuccess') }}}(fetch);
535
else successcb?.(fetch);
536
});
537
};
538
var storeError = (fetch, xhr, e) => {
539
#if FETCH_DEBUG
540
dbg('fetch: IndexedDB store failed.');
541
#endif
542
{{{ runtimeKeepalivePop() }}}
543
doCallback(() => {
544
if (onsuccess) {{{ makeDynCall('vp', 'onsuccess') }}}(fetch);
545
else successcb?.(fetch);
546
});
547
};
548
fetchCacheData(Fetch.dbInstance, fetch, xhr.response, storeSuccess, storeError);
549
};
550
551
var performCachedXhr = (fetch, xhr, e) => {
552
#if FETCH_DEBUG
553
dbg(`fetch: starting (cached) XHR: ${e}`);
554
#endif
555
fetchXHR(fetch, cacheResultAndReportSuccess, reportError, reportProgress, reportReadyStateChange);
556
};
557
558
var requestMethod = UTF8ToString(fetch_attr + {{{ C_STRUCTS.emscripten_fetch_attr_t.requestMethod }}});
559
var fetchAttrReplace = !!(fetchAttributes & {{{ cDefs.EMSCRIPTEN_FETCH_REPLACE }}});
560
var fetchAttrPersistFile = !!(fetchAttributes & {{{ cDefs.EMSCRIPTEN_FETCH_PERSIST_FILE }}});
561
var fetchAttrNoDownload = !!(fetchAttributes & {{{ cDefs.EMSCRIPTEN_FETCH_NO_DOWNLOAD }}});
562
if (requestMethod === 'EM_IDB_STORE') {
563
// TODO(?): Here we perform a clone of the data, because storing shared typed arrays to IndexedDB does not seem to be allowed.
564
var ptr = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.requestData, '*') }}};
565
var size = {{{ makeGetValue('fetch_attr', C_STRUCTS.emscripten_fetch_attr_t.requestDataSize, '*') }}};
566
fetchCacheData(Fetch.dbInstance, fetch, HEAPU8.slice(ptr, ptr + size), reportSuccess, reportError);
567
} else if (requestMethod === 'EM_IDB_DELETE') {
568
fetchDeleteCachedData(Fetch.dbInstance, fetch, reportSuccess, reportError);
569
} else if (!fetchAttrReplace) {
570
fetchLoadCachedData(Fetch.dbInstance, fetch, reportSuccess, fetchAttrNoDownload ? reportError : (fetchAttrPersistFile ? performCachedXhr : performUncachedXhr));
571
} else if (!fetchAttrNoDownload) {
572
fetchXHR(fetch, fetchAttrPersistFile ? cacheResultAndReportSuccess : reportSuccess, reportError, reportProgress, reportReadyStateChange);
573
} else {
574
#if FETCH_DEBUG
575
dbg('fetch: Invalid combination of flags passed.');
576
#endif
577
return 0; // todo: free
578
}
579
return fetch;
580
#else // !FETCH_SUPPORT_INDEXEDDB
581
fetchXHR(fetch, reportSuccess, reportError, reportProgress, reportReadyStateChange);
582
return fetch;
583
#endif // ~FETCH_SUPPORT_INDEXEDDB
584
}
585
586
function fetchGetResponseHeadersLength(id) {
587
return lengthBytesUTF8(Fetch.xhrs.get(id).getAllResponseHeaders());
588
}
589
590
function fetchGetResponseHeaders(id, dst, dstSizeBytes) {
591
var responseHeaders = Fetch.xhrs.get(id).getAllResponseHeaders();
592
return stringToUTF8(responseHeaders, dst, dstSizeBytes) + 1;
593
}
594
595
//Delete the xhr JS object, allowing it to be garbage collected.
596
function fetchFree(id) {
597
#if FETCH_DEBUG
598
dbg(`fetch: fetchFree id:${id}`);
599
#endif
600
if (Fetch.xhrs.has(id)) {
601
var xhr = Fetch.xhrs.get(id);
602
Fetch.xhrs.free(id);
603
// check if fetch is still in progress and should be aborted
604
if (xhr.readyState > 0 && xhr.readyState < 4) {
605
xhr.abort();
606
}
607
}
608
}
609
610