Skip to content

Commit

Permalink
polyfills: bundle our own fetch polyfill
Browse files Browse the repository at this point in the history
It's vendored and slightly adapted whatwg-fetch to start with.
  • Loading branch information
saghul committed Aug 21, 2024
1 parent 19ae6f3 commit 7e1cb9d
Show file tree
Hide file tree
Showing 10 changed files with 31,072 additions and 30,686 deletions.
11 changes: 0 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
"urlpattern-polyfill": "^10.0.0",
"uuid": "10.0.0",
"web-streams-polyfill": "^4.0.0",
"whatwg-fetch": "^3.6.20",
"whatwg-url": "^14.0.0"
},
"devDependencies": {
Expand Down
61,164 changes: 30,493 additions & 30,671 deletions src/bundles/c/core/polyfills.c

Large diffs are not rendered by default.

195 changes: 195 additions & 0 deletions src/js/polyfills/fetch/body.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
function isDataView(obj) {
return obj && isPrototypeOf(DataView.prototype, obj);
}

function consumed(body) {
if (body._noBody) {
return;
}

if (body.bodyUsed) {
return Promise.reject(new TypeError('Already read'));
}

body.bodyUsed = true;
}

function fileReaderReady(reader) {
return new Promise(function(resolve, reject) {
reader.onload = function() {
resolve(reader.result);
};

reader.onerror = function() {
reject(reader.error);
};
});
}

function readBlobAsArrayBuffer(blob) {
var reader = new FileReader();
var promise = fileReaderReady(reader);

reader.readAsArrayBuffer(blob);

return promise;
}

function readBlobAsText(blob) {
var reader = new FileReader();
var promise = fileReaderReady(reader);
var match = /charset=([A-Za-z0-9_-]+)/.exec(blob.type);
var encoding = match ? match[1] : 'utf-8';

reader.readAsText(blob, encoding);

return promise;
}

function readArrayBufferAsText(buf) {
var view = new Uint8Array(buf);
var chars = new Array(view.length);

for (var i = 0; i < view.length; i++) {
chars[i] = String.fromCharCode(view[i]);
}

return chars.join('');
}

function bufferClone(buf) {
if (buf.slice) {
return buf.slice(0);
} else {
const view = new Uint8Array(buf.byteLength);

view.set(new Uint8Array(buf));

return view.buffer;
}
}

function isPrototypeOf(a, b) {
return Object.prototype.isPrototypeOf.call(a, b);
}

export const BodyMixin = {
bodyUsed: false,

_initBody(body) {
this._bodyInit = body;

if (!body) {
this._noBody = true;
this._bodyText = '';
} else if (typeof body === 'string') {
this._bodyText = body;
} else if (isPrototypeOf(Blob.prototype, body)) {
this._bodyBlob = body;
} else if (isPrototypeOf(FormData.prototype, body)) {
this._bodyFormData = body;
} else if (isPrototypeOf(URLSearchParams.prototype, body)) {
this._bodyText = body.toString();
} else if (isDataView(body)) {
this._bodyArrayBuffer = bufferClone(body.buffer);
} else if (isPrototypeOf(ArrayBuffer.prototype, body) || ArrayBuffer.isView(body)) {
this._bodyArrayBuffer = bufferClone(body);
} else {
this._bodyText = body = Object.prototype.toString.call(body);
}

if (!this.headers.get('content-type')) {
if (typeof body === 'string') {
this.headers.set('content-type', 'text/plain;charset=UTF-8');
} else if (this._bodyBlob && this._bodyBlob.type) {
this.headers.set('content-type', this._bodyBlob.type);
} else if (isPrototypeOf(URLSearchParams.prototype, body)) {
this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
}
}
},

blob() {
const rejected = consumed(this);

if (rejected) {
return rejected;
}

if (this._bodyBlob) {
return Promise.resolve(this._bodyBlob);
} else if (this._bodyArrayBuffer) {
return Promise.resolve(new Blob([ this._bodyArrayBuffer ]));
} else if (this._bodyFormData) {
throw new Error('could not read FormData body as blob');
} else {
return Promise.resolve(new Blob([ this._bodyText ]));
}
},

arrayBuffer() {
if (this._bodyArrayBuffer) {
var isConsumed = consumed(this);

if (isConsumed) {
return isConsumed;
} else if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
return Promise.resolve(
this._bodyArrayBuffer.buffer.slice(
this._bodyArrayBuffer.byteOffset,
this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
)
);
} else {
return Promise.resolve(this._bodyArrayBuffer);
}
} else {
return this.blob().then(readBlobAsArrayBuffer);
}
},

text() {
const rejected = consumed(this);

if (rejected) {
return rejected;
}

if (this._bodyBlob) {
return readBlobAsText(this._bodyBlob);
} else if (this._bodyArrayBuffer) {
return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer));
} else if (this._bodyFormData) {
throw new Error('could not read FormData body as text');
} else {
return Promise.resolve(this._bodyText);
}
},

formData() {
return this.text().then(decode);
},

json() {
return this.text().then(JSON.parse);
},
};

function decode(body) {
const form = new FormData();

body
.trim()
.split('&')
.forEach(function(bytes) {
if (bytes) {
const split = bytes.split('=');
const name = split.shift().replace(/\+/g, ' ');
const value = split.join('=').replace(/\+/g, ' ');

form.append(decodeURIComponent(name), decodeURIComponent(value));
}
});

return form;
}
120 changes: 120 additions & 0 deletions src/js/polyfills/fetch/fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { Headers, normalizeName, normalizeValue } from './headers.js';
import { Request } from './request.js';


export function fetch(input, init) {
return new Promise(function(resolve, reject) {
const request = new Request(input, init);

if (request.signal && request.signal.aborted) {
return reject(new DOMException('Aborted', 'AbortError'));
}

const xhr = new XMLHttpRequest();

function abortXhr() {
xhr.abort();
}

xhr.onload = function() {
const options = {
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders() || ''),
status: xhr.status,
url: xhr.responseURL
};
const body = xhr.response;

setTimeout(function() {
resolve(new Response(body, options));
}, 0);
};

xhr.onerror = function() {
setTimeout(function() {
reject(new TypeError('Network request failed'));
}, 0);
};

xhr.ontimeout = function() {
setTimeout(function() {
reject(new TypeError('Network request timed out'));
}, 0);
};

xhr.onabort = function() {
setTimeout(function() {
reject(new DOMException('Aborted', 'AbortError'));
}, 0);
};

xhr.open(request.method, request.url, true);

if (request.credentials === 'include') {
xhr.withCredentials = true;
} else if (request.credentials === 'omit') {
xhr.withCredentials = false;
}

// TODO: better use Blob, if / when we support that.
xhr.responseType = 'arraybuffer';

if (init && typeof init.headers === 'object' && !(init.headers instanceof Headers)) {
const names = [];

Object.getOwnPropertyNames(init.headers).forEach(function(name) {
names.push(normalizeName(name));
xhr.setRequestHeader(name, normalizeValue(init.headers[name]));
});
request.headers.forEach(function(value, name) {
if (names.indexOf(name) === -1) {
xhr.setRequestHeader(name, value);
}
});
} else {
request.headers.forEach(function(value, name) {
xhr.setRequestHeader(name, value);
});
}

if (request.signal) {
request.signal.addEventListener('abort', abortXhr);

xhr.onreadystatechange = function() {
// DONE (success or failure)
if (xhr.readyState === xhr.DONE) {
request.signal.removeEventListener('abort', abortXhr);
}
};
}

// TODO: why not use the .body property?
xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit);
});
}


function parseHeaders(rawHeaders) {
const headers = new Headers();

// Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
// https://tools.ietf.org/html/rfc7230#section-3.2
const preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ');

preProcessedHeaders.split(/\r?\n/).forEach(line => {
const parts = line.split(':');
const key = parts.shift().trim();

if (key) {
const value = parts.join(':').trim();

try {
headers.append(key, value);
} catch (error) {
console.warn('Response ' + error.message);
}
}
});

return headers;
}
Loading

0 comments on commit 7e1cb9d

Please sign in to comment.