Open mydea opened 4 years ago
This is kind blocking. Without pretender fetch('/image.png')
returns corrent blob with correct size. With pretender it does not.
Any solution found @mydea ? I made a entire app with pretender and found that issue too..
No, sadly not!
I started to look at pretender source code and have found a solution. In create-passthrough.ts#L28 just check blob and arraybuffer response types, this solves the main issue with blob's. I found another issues, some events are firing twice and when using axios the event "readystatechange" are not firing properly depending on implementation. So, basically needs to replace the createPassthrough function from the file create-passthrough.ts with the changes (Changes explained prefixed by ### in comments):
function createPassthrough(fakeXHR, nativeXMLHttpRequest) {
// event types to handle on the xhr
var evts = ['error', 'timeout', 'abort', 'readystatechange'];
// event types to handle on the xhr.upload
var uploadEvents = [];
// properties to copy from the native xhr to fake xhr
var lifecycleProps = [
'readyState',
'responseText',
'response',
'responseXML',
'responseURL',
'status',
'statusText',
];
var xhr = (fakeXHR._passthroughRequest = new nativeXMLHttpRequest());
xhr.open(fakeXHR.method, fakeXHR.url, fakeXHR.async, fakeXHR.username, fakeXHR.password);
// if (fakeXHR.responseType === 'arraybuffer') {
//### check for blob either
if (~['blob', 'arraybuffer'].indexOf(fakeXHR.responseType)) {
lifecycleProps = ['readyState', 'response', 'status', 'statusText'];
xhr.responseType = fakeXHR.responseType;
}
// use onload if the browser supports it
if ('onload' in xhr) {
evts.push('load');
}
// add progress event for async calls
// avoid using progress events for sync calls, they will hang https://bugs.webkit.org/show_bug.cgi?id=40996.
// if (fakeXHR.async && fakeXHR.responseType !== 'arraybuffer') {
//### just check for async request
if (fakeXHR.async) {
evts.push('progress');
uploadEvents.push('progress');
}
// update `propertyNames` properties from `fromXHR` to `toXHR`
function copyLifecycleProperties(propertyNames, fromXHR, toXHR) {
for (var i = 0; i < propertyNames.length; i++) {
var prop = propertyNames[i];
if (prop in fromXHR) {
toXHR[prop] = fromXHR[prop];
}
}
}
// fire fake event on `eventable`
function dispatchEvent(fakeXHR, eventType, event) {
//### only dispatchEvent, not manual firing events
fakeXHR.dispatchEvent(event);
// if (fakeXHR['on' + eventType]) {
//
// // fakeXHR['on' + eventType](event);
// }
}
// set the on- handler on the native xhr for the given eventType
function createHandler(eventType) {
//### delete manual addition of events and re-adding the listener to dispatchEvent always work properly
const fakeEventKey = 'on'+eventType;
if(fakeXHR[fakeEventKey]){
const fn = fakeXHR[fakeEventKey];
delete fakeXHR[fakeEventKey];
fakeXHR.addEventListener(eventType, fn);
}
xhr.addEventListener(eventType, function (event) {
copyLifecycleProperties(lifecycleProps, xhr, fakeXHR);
dispatchEvent(fakeXHR, eventType, event);
});
//###
// xhr['on' + eventType] = function (event) {
// copyLifecycleProperties(lifecycleProps, xhr, fakeXHR);
// dispatchEvent(fakeXHR, eventType, event);
// };
}
// set the on- handler on the native xhr's `upload` property for
// the given eventType
function createUploadHandler(eventType) {
// if (xhr.upload && fakeXHR.upload && fakeXHR.upload['on' + eventType]) {
if (xhr.upload && fakeXHR.upload) {
//### delete manual addition of events and re-adding the listener to dispatchEvent always work properly
const fakeEventKey = 'on'+eventType;
if(fakeXHR.upload[fakeEventKey]){
const fn = fakeXHR.upload[fakeEventKey];
delete fakeXHR.upload[fakeEventKey];
fakeXHR.upload.addEventListener(eventType, fn);
}
xhr.upload.addEventListener(eventType, function (event) {
dispatchEvent(fakeXHR.upload, eventType, event);
});
//###
// xhr.upload['on' + eventType] = function (event) {
// dispatchEvent(fakeXHR.upload, eventType, event);
// };
}
}
var i;
for (i = 0; i < evts.length; i++) {
createHandler(evts[i]);
}
for (i = 0; i < uploadEvents.length; i++) {
createUploadHandler(uploadEvents[i]);
}
if (fakeXHR.async) {
xhr.timeout = fakeXHR.timeout;
xhr.withCredentials = fakeXHR.withCredentials;
}
for (var h in fakeXHR.requestHeaders) {
xhr.setRequestHeader(h, fakeXHR.requestHeaders[h]);
}
return xhr;
}
And my tests code:
import axios from 'axios';
import Pretender from 'pretender';
const server = new Pretender(function() {
this.get('/test', () => {
return [200, {}, {data: {foo: 'bar'}}]
});
});
server.unhandledRequest = function(verb, path, request) {
const xhr = request.passthrough();
xhr.onloadend = (ev) => {
console.warn(`Response for ${path}`, {
verb,
path,
request,
responseEvent: ev,
})
};
};
const urls = [
'https://static.vecteezy.com/system/resources/thumbnails/000/246/312/original/mountain-lake-sunset-landscape-first-person-view.jpg',
'https://speed.hetzner.de/100MB.bin',
'https://speed.hetzner.de/1GB.bin'
];
const url = urls[0];
const postUrl = `https://postman-echo.com/post`;
function buildPostFormData(){
//Some form size to show upload progress process better
const formData = new FormData();
for(let x = 0; x < 5000; x++){
formData.append(x, 'My data ' + x);
}
return formData;
}
const tests = {
native: {
get(){
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onprogress = function(e){
console.log('ondownloadprogress', e);
};
xhr.onload = function (event) {
console.log('load done', xhr.response);
if(url === urls[0]) {
const urlCreator = window.URL || window.webkitURL;
const imageUrl = URL.createObjectURL(this.response);
const el = document.createElement('img');
el.src = imageUrl;
document.body.append(el);
}
}
xhr.send();
},
post(){
const xhr = new XMLHttpRequest();
xhr.open('POST', postUrl, true);
xhr.onprogress = (e) => {
console.log('ondownloadprogress', e)
}
xhr.upload.onprogress = (e) => {
console.log('onuploadprogress', e)
};
// xhr.upload.addEventListener('progress', (e) => {
//
// console.log('onuploadprogress 2', e)
//
// }, false);
//
// xhr.upload.addEventListener('progress', (e) => {
//
// console.log('onuploadprogress 3', e)
//
// }, false);
xhr.onload = () => {
console.log('done upload')
}
xhr.send(buildPostFormData());
}
},
axios: {
get(){
axios.get(url, {
responseType: 'blob',
onDownloadProgress(e){
console.log('ondownloadprogress', e)
},
onUploadProgress(e){
console.log('onuploadprogress', e);
}
}).then(result => {
console.log(result);
if(url === urls[0]) {
const urlCreator = window.URL || window.webkitURL;
const imageUrl = URL.createObjectURL(result.data);
const el = document.createElement('img');
el.src = imageUrl;
document.body.append(el);
}
}).catch(e => {
console.log(e);
})
},
post(){
axios.post(
postUrl,
buildPostFormData(),
{
headers: {
'Content-Type': 'multipart/form-data'
},
onDownloadProgress(e){
console.log('ondownloadprogress', e)
},
onUploadProgress(e){
console.log('onuploadprogress', e);
}
}).then(res => {
console.log(res);
}).catch(e => {
console.log(e);
});
}
}
};
tests.native.get();
One more thing. In order to download files from web in localhost, its going to be needed to disabled browser CORS policy, in case of chrome see Disable CORS Chrome.
Hope it helps @mydea! Regards.
@ankology nice finding! Would you mind creat a PR with your change?
I was facing this issue when using mirage js with pdf js I found where pretender storing original fetch, so created a workaround function
export default async function useNativeFetch(anyFunc) {
const savedFetch = window.fetch;
window.fetch = mirageServer.pretender._nativefetch;
const res = await anyFunc();
window.fetch = savedFetch;
return res;
}
using it like this
const pdf = await useNativeFetch(
() => pdfjsLib.getDocument(pdfUrl).promise
);
https://github.com/pretenderjs/pretender/issues/305#issuecomment-685815226
Would be fantatsic if this was made into a PR, major bummer
PR here with Ankology's fix https://github.com/pretenderjs/pretender/pull/363
I just spent many hours trying to figure out why a binary file sent via an API was always corrupted, until I found out it was because of Pretender passthrough somehow bungling it up.
I searched and found this: https://github.com/pretenderjs/pretender/pull/148, but as far as I see this cannot be set when using
fetch()
.