mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
feat(http): better binary support & XHR support (#7707)
* feat(http): binary upload support * feat(http): better binary support & XHR support * fix: linting issue * chore: moved files from old place to the new one * chore: Updated NativeScript.api.md * feat(http): support both ByteBuffer and String Co-authored-by: Vasil Trifonov <v.trifonov@gmail.com>
This commit is contained in:
committed by
Vasil Trifonov
parent
a311a922b5
commit
e293367dfc
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2014-2015 GitHub, Inc.
|
||||
Copyright (c) 2014-2016 GitHub, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -1,336 +1,539 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
exports.XMLHttpRequest = global.XMLHttpRequest;
|
||||
exports.FormData = global.FormData;
|
||||
var support = {
|
||||
searchParams: "URLSearchParams" in global,
|
||||
iterable: "Symbol" in global && "iterator" in Symbol,
|
||||
blob:
|
||||
"FileReader" in global &&
|
||||
"Blob" in global &&
|
||||
(function () {
|
||||
try {
|
||||
new Blob();
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
})(),
|
||||
formData: "FormData" in global,
|
||||
arrayBuffer: "ArrayBuffer" in global
|
||||
};
|
||||
|
||||
if (!exports.XMLHttpRequest) {
|
||||
var xhr = require("../xhr");
|
||||
exports.XMLHttpRequest = xhr.XMLHttpRequest;
|
||||
exports.FormData = xhr.FormData;
|
||||
function isDataView(obj) {
|
||||
return obj && DataView.prototype.isPrototypeOf(obj);
|
||||
}
|
||||
|
||||
if (support.arrayBuffer) {
|
||||
var viewClasses = [
|
||||
"[object Int8Array]",
|
||||
"[object Uint8Array]",
|
||||
"[object Uint8ClampedArray]",
|
||||
"[object Int16Array]",
|
||||
"[object Uint16Array]",
|
||||
"[object Int32Array]",
|
||||
"[object Uint32Array]",
|
||||
"[object Float32Array]",
|
||||
"[object Float64Array]"
|
||||
];
|
||||
|
||||
var isArrayBufferView =
|
||||
ArrayBuffer.isView ||
|
||||
function (obj) {
|
||||
return (
|
||||
obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeName(name) {
|
||||
if (typeof name !== 'string') {
|
||||
name = name.toString();
|
||||
if (typeof name !== "string") {
|
||||
name = String(name);
|
||||
}
|
||||
if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) {
|
||||
throw new TypeError('Invalid character in header field name')
|
||||
if (/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(name)) {
|
||||
throw new TypeError("Invalid character in header field name");
|
||||
}
|
||||
return name.toLowerCase()
|
||||
return name.toLowerCase();
|
||||
}
|
||||
|
||||
function normalizeValue(value) {
|
||||
if (typeof value !== 'string') {
|
||||
value = value.toString();
|
||||
if (typeof value !== "string") {
|
||||
value = String(value);
|
||||
}
|
||||
return value
|
||||
return value;
|
||||
}
|
||||
|
||||
// Build a destructive iterator for the value list
|
||||
function iteratorFor(items) {
|
||||
var iterator = {
|
||||
next: function () {
|
||||
var value = items.shift();
|
||||
return { done: value === undefined, value: value };
|
||||
}
|
||||
};
|
||||
|
||||
if (support.iterable) {
|
||||
iterator[Symbol.iterator] = function () {
|
||||
return iterator;
|
||||
};
|
||||
}
|
||||
|
||||
return iterator;
|
||||
}
|
||||
|
||||
function Headers(headers) {
|
||||
this.map = {}
|
||||
this.map = {};
|
||||
|
||||
if (headers instanceof Headers) {
|
||||
headers.forEach(function (value, name) {
|
||||
this.append(name, value)
|
||||
}, this)
|
||||
|
||||
this.append(name, value);
|
||||
}, this);
|
||||
} else if (Array.isArray(headers)) {
|
||||
headers.forEach(function (header) {
|
||||
this.append(header[0], header[1]);
|
||||
}, this);
|
||||
} else if (headers) {
|
||||
Object.getOwnPropertyNames(headers).forEach(function (name) {
|
||||
this.append(name, headers[name])
|
||||
}, this)
|
||||
this.append(name, headers[name]);
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
Headers.prototype.append = function (name, value) {
|
||||
name = normalizeName(name)
|
||||
value = normalizeValue(value)
|
||||
var list = this.map[name]
|
||||
if (!list) {
|
||||
list = []
|
||||
this.map[name] = list
|
||||
}
|
||||
list.push(value)
|
||||
}
|
||||
name = normalizeName(name);
|
||||
value = normalizeValue(value);
|
||||
var oldValue = this.map[name];
|
||||
this.map[name] = oldValue ? oldValue + ", " + value : value;
|
||||
};
|
||||
|
||||
Headers.prototype['delete'] = function (name) {
|
||||
delete this.map[normalizeName(name)]
|
||||
}
|
||||
Headers.prototype["delete"] = function (name) {
|
||||
delete this.map[normalizeName(name)];
|
||||
};
|
||||
|
||||
Headers.prototype.get = function (name) {
|
||||
var values = this.map[normalizeName(name)]
|
||||
return values ? values[0] : null
|
||||
}
|
||||
|
||||
Headers.prototype.getAll = function (name) {
|
||||
return this.map[normalizeName(name)] || []
|
||||
}
|
||||
name = normalizeName(name);
|
||||
return this.has(name) ? this.map[name] : null;
|
||||
};
|
||||
|
||||
Headers.prototype.has = function (name) {
|
||||
return this.map.hasOwnProperty(normalizeName(name))
|
||||
}
|
||||
return this.map.hasOwnProperty(normalizeName(name));
|
||||
};
|
||||
|
||||
Headers.prototype.set = function (name, value) {
|
||||
this.map[normalizeName(name)] = [normalizeValue(value)]
|
||||
}
|
||||
this.map[normalizeName(name)] = normalizeValue(value);
|
||||
};
|
||||
|
||||
Headers.prototype.forEach = function (callback, thisArg) {
|
||||
Object.getOwnPropertyNames(this.map).forEach(function (name) {
|
||||
this.map[name].forEach(function (value) {
|
||||
callback.call(thisArg, value, name, this)
|
||||
}, this)
|
||||
}, this)
|
||||
for (var name in this.map) {
|
||||
if (this.map.hasOwnProperty(name)) {
|
||||
callback.call(thisArg, this.map[name], name, this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Headers.prototype.keys = function () {
|
||||
var items = [];
|
||||
this.forEach(function (value, name) {
|
||||
items.push(name);
|
||||
});
|
||||
return iteratorFor(items);
|
||||
};
|
||||
|
||||
Headers.prototype.values = function () {
|
||||
var items = [];
|
||||
this.forEach(function (value) {
|
||||
items.push(value);
|
||||
});
|
||||
return iteratorFor(items);
|
||||
};
|
||||
|
||||
Headers.prototype.entries = function () {
|
||||
var items = [];
|
||||
this.forEach(function (value, name) {
|
||||
items.push([name, value]);
|
||||
});
|
||||
return iteratorFor(items);
|
||||
};
|
||||
|
||||
if (support.iterable) {
|
||||
Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
|
||||
}
|
||||
|
||||
function consumed(body) {
|
||||
if (body.bodyUsed) {
|
||||
return Promise.reject(new TypeError('Already read'))
|
||||
return Promise.reject(new TypeError("Already read"));
|
||||
}
|
||||
body.bodyUsed = true
|
||||
body.bodyUsed = true;
|
||||
}
|
||||
|
||||
function fileReaderReady(reader) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
reader.onload = function () {
|
||||
resolve(reader.result)
|
||||
}
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.onerror = function () {
|
||||
reject(reader.error)
|
||||
}
|
||||
})
|
||||
reject(reader.error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function readBlobAsArrayBuffer(blob) {
|
||||
var reader = new FileReader()
|
||||
reader.readAsArrayBuffer(blob)
|
||||
return fileReaderReady(reader)
|
||||
var reader = new FileReader();
|
||||
var promise = fileReaderReady(reader);
|
||||
reader.readAsArrayBuffer(blob);
|
||||
return promise;
|
||||
}
|
||||
|
||||
function readBlobAsText(blob) {
|
||||
var reader = new FileReader()
|
||||
reader.readAsText(blob)
|
||||
return fileReaderReady(reader)
|
||||
var reader = new FileReader();
|
||||
var promise = fileReaderReady(reader);
|
||||
reader.readAsText(blob);
|
||||
return promise;
|
||||
}
|
||||
|
||||
var support = {
|
||||
blob: 'FileReader' in exports && 'Blob' in exports && (function () {
|
||||
try {
|
||||
new Blob();
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
})(),
|
||||
formData: 'FormData' in exports
|
||||
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 {
|
||||
var view = new Uint8Array(buf.byteLength);
|
||||
view.set(new Uint8Array(buf));
|
||||
return view.buffer;
|
||||
}
|
||||
}
|
||||
|
||||
function Body() {
|
||||
this.bodyUsed = false
|
||||
|
||||
this.bodyUsed = false;
|
||||
|
||||
this._initBody = function (body) {
|
||||
this._bodyInit = body
|
||||
if (typeof body === 'string') {
|
||||
this._bodyText = body
|
||||
this._bodyInit = body;
|
||||
if (!body) {
|
||||
this._bodyText = "";
|
||||
} else if (typeof body === "string") {
|
||||
this._bodyText = body;
|
||||
} else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
|
||||
this._bodyBlob = body
|
||||
} else if (support.formData && exports.FormData.prototype.isPrototypeOf(body)) {
|
||||
this._bodyFormData = body
|
||||
} else if (!body) {
|
||||
this._bodyText = ''
|
||||
this._bodyBlob = body;
|
||||
} else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
|
||||
this._bodyFormData = body;
|
||||
} else if (
|
||||
support.searchParams &&
|
||||
URLSearchParams.prototype.isPrototypeOf(body)
|
||||
) {
|
||||
this._bodyText = body.toString();
|
||||
} else if (support.arrayBuffer && support.blob && isDataView(body)) {
|
||||
this._bodyArrayBuffer = bufferClone(body.buffer);
|
||||
// IE 10-11 can't handle a DataView body.
|
||||
this._bodyInit = new Blob([this._bodyArrayBuffer]);
|
||||
} else if (
|
||||
support.arrayBuffer &&
|
||||
(ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))
|
||||
) {
|
||||
this._bodyArrayBuffer = bufferClone(body);
|
||||
} else {
|
||||
throw new Error('unsupported BodyInit type')
|
||||
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 (
|
||||
support.searchParams &&
|
||||
URLSearchParams.prototype.isPrototypeOf(body)
|
||||
) {
|
||||
this.headers.set(
|
||||
"content-type",
|
||||
"application/x-www-form-urlencoded;charset=UTF-8"
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (support.blob) {
|
||||
this.blob = function () {
|
||||
var rejected = consumed(this)
|
||||
var rejected = consumed(this);
|
||||
if (rejected) {
|
||||
return rejected
|
||||
return rejected;
|
||||
}
|
||||
|
||||
if (this._bodyBlob) {
|
||||
return Promise.resolve(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')
|
||||
throw new Error("could not read FormData body as blob");
|
||||
} else {
|
||||
return Promise.resolve(new Blob([this._bodyText]))
|
||||
return Promise.resolve(new Blob([this._bodyText]));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.arrayBuffer = function () {
|
||||
return this.blob().then(readBlobAsArrayBuffer)
|
||||
}
|
||||
|
||||
this.text = function () {
|
||||
var rejected = consumed(this)
|
||||
if (rejected) {
|
||||
return rejected
|
||||
}
|
||||
|
||||
if (this._bodyBlob) {
|
||||
return readBlobAsText(this._bodyBlob)
|
||||
} else if (this._bodyFormData) {
|
||||
throw new Error('could not read FormData body as text')
|
||||
if (this._bodyArrayBuffer) {
|
||||
return consumed(this) || Promise.resolve(this._bodyArrayBuffer);
|
||||
} else {
|
||||
return Promise.resolve(this._bodyText)
|
||||
return this.blob().then(readBlobAsArrayBuffer);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.text = function () {
|
||||
var rejected = consumed(this)
|
||||
return rejected ? rejected : Promise.resolve(this._bodyText)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.text = function () {
|
||||
var 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);
|
||||
}
|
||||
};
|
||||
|
||||
if (support.formData) {
|
||||
this.formData = function () {
|
||||
return this.text().then(decode)
|
||||
}
|
||||
return this.text().then(decode);
|
||||
};
|
||||
}
|
||||
|
||||
this.json = function () {
|
||||
return this.text().then(JSON.parse)
|
||||
}
|
||||
return this.text().then(JSON.parse);
|
||||
};
|
||||
|
||||
return this
|
||||
return this;
|
||||
}
|
||||
|
||||
// HTTP methods whose capitalization should be normalized
|
||||
var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']
|
||||
var methods = ["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT"];
|
||||
|
||||
function normalizeMethod(method) {
|
||||
var upcased = method.toUpperCase()
|
||||
return (methods.indexOf(upcased) > -1) ? upcased : method
|
||||
var upcased = method.toUpperCase();
|
||||
return methods.indexOf(upcased) > -1 ? upcased : method;
|
||||
}
|
||||
|
||||
function Request(url, options) {
|
||||
options = options || {}
|
||||
this.url = url
|
||||
function Request(input, options) {
|
||||
options = options || {};
|
||||
var body = options.body;
|
||||
|
||||
this.credentials = options.credentials || 'omit'
|
||||
this.headers = new Headers(options.headers)
|
||||
this.method = normalizeMethod(options.method || 'GET')
|
||||
this.mode = options.mode || null
|
||||
this.referrer = null
|
||||
|
||||
if ((this.method === 'GET' || this.method === 'HEAD') && options.body) {
|
||||
throw new TypeError('Body not allowed for GET or HEAD requests')
|
||||
if (input instanceof Request) {
|
||||
if (input.bodyUsed) {
|
||||
throw new TypeError("Already read");
|
||||
}
|
||||
this.url = input.url;
|
||||
this.credentials = input.credentials;
|
||||
if (!options.headers) {
|
||||
this.headers = new Headers(input.headers);
|
||||
}
|
||||
this.method = input.method;
|
||||
this.mode = input.mode;
|
||||
this.signal = input.signal;
|
||||
if (!body && input._bodyInit != null) {
|
||||
body = input._bodyInit;
|
||||
input.bodyUsed = true;
|
||||
}
|
||||
} else {
|
||||
this.url = String(input);
|
||||
}
|
||||
this._initBody(options.body)
|
||||
|
||||
this.credentials = options.credentials || this.credentials || "same-origin";
|
||||
if (options.headers || !this.headers) {
|
||||
this.headers = new Headers(options.headers);
|
||||
}
|
||||
this.method = normalizeMethod(options.method || this.method || "GET");
|
||||
this.mode = options.mode || this.mode || null;
|
||||
this.signal = options.signal || this.signal;
|
||||
this.referrer = null;
|
||||
|
||||
if ((this.method === "GET" || this.method === "HEAD") && body) {
|
||||
throw new TypeError("Body not allowed for GET or HEAD requests");
|
||||
}
|
||||
this._initBody(body);
|
||||
}
|
||||
|
||||
Request.prototype.clone = function () {
|
||||
return new Request(this, { body: this._bodyInit });
|
||||
};
|
||||
|
||||
function decode(body) {
|
||||
var form = new exports.FormData()
|
||||
body.trim().split('&').forEach(function (bytes) {
|
||||
if (bytes) {
|
||||
var split = bytes.split('=')
|
||||
var name = split.shift().replace(/\+/g, ' ')
|
||||
var value = split.join('=').replace(/\+/g, ' ')
|
||||
form.append(decodeURIComponent(name), decodeURIComponent(value))
|
||||
var form = new FormData();
|
||||
body
|
||||
.trim()
|
||||
.split("&")
|
||||
.forEach(function (bytes) {
|
||||
if (bytes) {
|
||||
var split = bytes.split("=");
|
||||
var name = split.shift().replace(/\+/g, " ");
|
||||
var value = split.join("=").replace(/\+/g, " ");
|
||||
form.append(decodeURIComponent(name), decodeURIComponent(value));
|
||||
}
|
||||
});
|
||||
return form;
|
||||
}
|
||||
|
||||
function parseHeaders(rawHeaders) {
|
||||
var 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
|
||||
var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, " ");
|
||||
preProcessedHeaders.split(/\r?\n/).forEach(function (line) {
|
||||
var parts = line.split(":");
|
||||
var key = parts.shift().trim();
|
||||
if (key) {
|
||||
var value = parts.join(":").trim();
|
||||
headers.append(key, value);
|
||||
}
|
||||
})
|
||||
return form
|
||||
});
|
||||
return headers;
|
||||
}
|
||||
|
||||
function headers(xhr) {
|
||||
var head = new Headers()
|
||||
var pairs = xhr.getAllResponseHeaders().trim().split('\n')
|
||||
pairs.forEach(function (header) {
|
||||
var split = header.trim().split(':')
|
||||
var key = split.shift().trim()
|
||||
var value = split.join(':').trim()
|
||||
head.append(key, value)
|
||||
})
|
||||
return head
|
||||
}
|
||||
|
||||
Body.call(Request.prototype)
|
||||
Body.call(Request.prototype);
|
||||
|
||||
function Response(bodyInit, options) {
|
||||
if (!options) {
|
||||
options = {}
|
||||
options = {};
|
||||
}
|
||||
|
||||
this._initBody(bodyInit)
|
||||
this.type = 'default'
|
||||
this.url = null
|
||||
this.status = options.status
|
||||
this.ok = this.status >= 200 && this.status < 300
|
||||
this.statusText = options.statusText
|
||||
this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers)
|
||||
this.url = options.url || ''
|
||||
this.type = "default";
|
||||
this.status = options.status === undefined ? 200 : options.status;
|
||||
this.ok = this.status >= 200 && this.status < 300;
|
||||
this.statusText = "statusText" in options ? options.statusText : "OK";
|
||||
this.headers = new Headers(options.headers);
|
||||
this.url = options.url || "";
|
||||
this._initBody(bodyInit);
|
||||
}
|
||||
|
||||
Body.call(Response.prototype)
|
||||
Body.call(Response.prototype);
|
||||
|
||||
Response.prototype.clone = function () {
|
||||
return new Response(this._bodyInit, {
|
||||
status: this.status,
|
||||
statusText: this.statusText,
|
||||
headers: new Headers(this.headers),
|
||||
url: this.url
|
||||
});
|
||||
};
|
||||
|
||||
Response.error = function () {
|
||||
var response = new Response(null, { status: 0, statusText: "" });
|
||||
response.type = "error";
|
||||
return response;
|
||||
};
|
||||
|
||||
var redirectStatuses = [301, 302, 303, 307, 308];
|
||||
|
||||
Response.redirect = function (url, status) {
|
||||
if (redirectStatuses.indexOf(status) === -1) {
|
||||
throw new RangeError("Invalid status code");
|
||||
}
|
||||
|
||||
return new Response(null, { status: status, headers: { location: url } });
|
||||
};
|
||||
|
||||
exports.DOMException = global.DOMException;
|
||||
try {
|
||||
new exports.DOMException();
|
||||
} catch (err) {
|
||||
exports.DOMException = function (message, name) {
|
||||
this.message = message;
|
||||
this.name = name;
|
||||
var error = Error(message);
|
||||
this.stack = error.stack;
|
||||
};
|
||||
exports.DOMException.prototype = Object.create(Error.prototype);
|
||||
exports.DOMException.prototype.constructor = exports.DOMException;
|
||||
}
|
||||
|
||||
function fetch(input, init) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var request = new Request(input, init);
|
||||
|
||||
if (request.signal && request.signal.aborted) {
|
||||
return reject(new exports.DOMException("Aborted", "AbortError"));
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
function abortXhr() {
|
||||
xhr.abort();
|
||||
}
|
||||
|
||||
xhr.onload = function () {
|
||||
var options = {
|
||||
status: xhr.status,
|
||||
statusText: xhr.statusText,
|
||||
headers: parseHeaders(xhr.getAllResponseHeaders() || "")
|
||||
};
|
||||
options.url =
|
||||
"responseURL" in xhr
|
||||
? xhr.responseURL
|
||||
: options.headers.get("X-Request-URL");
|
||||
var body = "response" in xhr ? xhr.response : xhr.responseText;
|
||||
resolve(new Response(body, options));
|
||||
};
|
||||
|
||||
xhr.onerror = function () {
|
||||
reject(new TypeError("Network request failed"));
|
||||
};
|
||||
|
||||
xhr.ontimeout = function () {
|
||||
reject(new TypeError("Network request failed"));
|
||||
};
|
||||
|
||||
xhr.onabort = function () {
|
||||
reject(new exports.DOMException("Aborted", "AbortError"));
|
||||
};
|
||||
|
||||
xhr.open(request.method, request.url, true);
|
||||
|
||||
if (request.credentials === "include") {
|
||||
xhr.withCredentials = true;
|
||||
} else if (request.credentials === "omit") {
|
||||
xhr.withCredentials = false;
|
||||
}
|
||||
|
||||
if ("responseType" in xhr && support.blob) {
|
||||
xhr.responseType = "blob";
|
||||
}
|
||||
|
||||
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 === 4) {
|
||||
request.signal.removeEventListener("abort", abortXhr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
xhr.send(
|
||||
typeof request._bodyInit === "undefined" ? null : request._bodyInit
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fetch.polyfill = true;
|
||||
|
||||
exports.Headers = Headers;
|
||||
exports.Request = Request;
|
||||
exports.Response = Response;
|
||||
exports.fetch = fetch;
|
||||
|
||||
exports.fetch = function (input, init) {
|
||||
// TODO: Request constructor should accept input, init
|
||||
var request
|
||||
if (Request.prototype.isPrototypeOf(input) && !init) {
|
||||
request = input
|
||||
} else {
|
||||
request = new Request(input, init)
|
||||
}
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
var xhr = new exports.XMLHttpRequest()
|
||||
|
||||
function responseURL() {
|
||||
if ('responseURL' in xhr) {
|
||||
return xhr.responseURL
|
||||
}
|
||||
|
||||
// Avoid security warnings on getResponseHeader when not allowed by CORS
|
||||
if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) {
|
||||
return xhr.getResponseHeader('X-Request-URL')
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
xhr.onload = function () {
|
||||
var status = (xhr.status === 1223) ? 204 : xhr.status
|
||||
if (status < 100 || status > 599) {
|
||||
reject(new TypeError('Network request failed'))
|
||||
return
|
||||
}
|
||||
var options = {
|
||||
status: status,
|
||||
statusText: xhr.statusText,
|
||||
headers: headers(xhr),
|
||||
url: responseURL()
|
||||
}
|
||||
//var body = 'response' in xhr ? xhr.response : xhr.responseText;
|
||||
resolve(new Response(xhr.responseText, options))
|
||||
}
|
||||
|
||||
xhr.onerror = function (error) {
|
||||
reject(new TypeError(['Network request failed:', error.message].join(' ')))
|
||||
}
|
||||
|
||||
xhr.open(request.method, request.url, true)
|
||||
|
||||
if (request.credentials === 'include') {
|
||||
xhr.withCredentials = true
|
||||
}
|
||||
|
||||
if ('responseType' in xhr && support.blob) {
|
||||
xhr.responseType = 'blob'
|
||||
}
|
||||
|
||||
request.headers.forEach(function (value, name) {
|
||||
xhr.setRequestHeader(name, value)
|
||||
})
|
||||
|
||||
xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
|
||||
})
|
||||
}
|
||||
exports.fetch.polyfill = true
|
||||
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
})();
|
||||
|
||||
@@ -3,6 +3,7 @@ import "./core";
|
||||
import "./polyfills/timers";
|
||||
import "./polyfills/animation";
|
||||
import "./polyfills/dialogs";
|
||||
import "./polyfills/text";
|
||||
import "./polyfills/xhr";
|
||||
import "./polyfills/fetch";
|
||||
|
||||
|
||||
5
nativescript-core/globals/polyfills/text/package.json
Normal file
5
nativescript-core/globals/polyfills/text/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name" : "text",
|
||||
"main" : "text",
|
||||
"nativescript": {}
|
||||
}
|
||||
8
nativescript-core/globals/polyfills/text/text.ts
Normal file
8
nativescript-core/globals/polyfills/text/text.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import "../../core";
|
||||
import "../../polyfills/xhr";
|
||||
|
||||
import { installPolyfills } from "../polyfill-helpers";
|
||||
|
||||
global.registerModule("text", () => require("../../../text"));
|
||||
|
||||
installPolyfills("text", ["TextDecoder", "TextEncoder"]);
|
||||
@@ -3,4 +3,4 @@ import { installPolyfills } from "../polyfill-helpers";
|
||||
|
||||
global.registerModule("xhr", () => require("../../../xhr"));
|
||||
|
||||
installPolyfills("xhr", ["XMLHttpRequest", "FormData"]);
|
||||
installPolyfills("xhr", ["XMLHttpRequest", "FormData", "Blob", "File", "FileReader"]);
|
||||
|
||||
@@ -92,6 +92,7 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A
|
||||
callbacks.resolveCallback({
|
||||
content: {
|
||||
raw: result.raw,
|
||||
toArrayBuffer: () => Uint8Array.from(result.raw.toByteArray()).buffer,
|
||||
toString: (encoding?: HttpResponseEncoding) => {
|
||||
let str: string;
|
||||
if (encoding) {
|
||||
@@ -180,7 +181,14 @@ function buildJavaOptions(options: httpModule.HttpRequestOptions) {
|
||||
javaOptions.method = options.method;
|
||||
}
|
||||
if (typeof options.content === "string" || options.content instanceof FormData) {
|
||||
javaOptions.content = options.content.toString();
|
||||
const nativeString = new java.lang.String(options.content.toString());
|
||||
const nativeBytes = nativeString.getBytes("UTF-8");
|
||||
const nativeBuffer = java.nio.ByteBuffer.wrap(nativeBytes);
|
||||
javaOptions.content = nativeBuffer;
|
||||
} else if (options.content instanceof ArrayBuffer) {
|
||||
const typedArray = new Uint8Array(options.content as ArrayBuffer);
|
||||
const nativeBuffer = java.nio.ByteBuffer.wrap(Array.from(typedArray));
|
||||
javaOptions.content = nativeBuffer;
|
||||
}
|
||||
if (typeof options.timeout === "number") {
|
||||
javaOptions.timeout = options.timeout;
|
||||
|
||||
@@ -95,6 +95,9 @@ export function request(options: httpModule.HttpRequestOptions): Promise<httpMod
|
||||
|
||||
if (types.isString(options.content) || options.content instanceof FormData) {
|
||||
urlRequest.HTTPBody = NSString.stringWithString(options.content.toString()).dataUsingEncoding(4);
|
||||
} else if (options.content instanceof ArrayBuffer) {
|
||||
const buffer = options.content as ArrayBuffer;
|
||||
urlRequest.HTTPBody = NSData.dataWithData(buffer as any);
|
||||
}
|
||||
|
||||
if (types.isNumber(options.timeout)) {
|
||||
@@ -142,7 +145,15 @@ export function request(options: httpModule.HttpRequestOptions): Promise<httpMod
|
||||
resolve({
|
||||
content: {
|
||||
raw: data,
|
||||
toString: (encoding?: any) => NSDataToString(data, encoding),
|
||||
toArrayBuffer: () => interop.bufferFromData(data),
|
||||
toString: (encoding?: any) => {
|
||||
const str = NSDataToString(data, encoding);
|
||||
if (typeof str === "string") {
|
||||
return str;
|
||||
} else {
|
||||
throw new Error("Response content may not be converted to string");
|
||||
}
|
||||
},
|
||||
toJSON: (encoding?: any) => parseJSON(NSDataToString(data, encoding)),
|
||||
toImage: () => {
|
||||
ensureImageSource();
|
||||
|
||||
19
nativescript-core/http/http.d.ts
vendored
19
nativescript-core/http/http.d.ts
vendored
@@ -56,6 +56,18 @@ export function getFile(url: string, destinationFilePath?: string): Promise<File
|
||||
*/
|
||||
export function getFile(options: HttpRequestOptions, destinationFilePath?: string): Promise<File>;
|
||||
|
||||
/**
|
||||
* Downloads the content from the specified URL as binary and returns an ArrayBuffer.
|
||||
* @param url The URL to request from.
|
||||
*/
|
||||
export function getBinary(url: string): Promise<ArrayBuffer>;
|
||||
|
||||
/**
|
||||
* Downloads the content from the specified URL as binary and returns an ArrayBuffer.
|
||||
* @param options An object that specifies various request options.
|
||||
*/
|
||||
export function getBinary(options: HttpRequestOptions): Promise<ArrayBuffer>;
|
||||
|
||||
/**
|
||||
* Makes a generic http request using the provided options and returns a HttpResponse Object.
|
||||
* @param options An object that specifies various request options.
|
||||
@@ -84,7 +96,7 @@ export interface HttpRequestOptions {
|
||||
/**
|
||||
* Gets or sets the request body.
|
||||
*/
|
||||
content?: string | FormData;
|
||||
content?: string | FormData | ArrayBuffer;
|
||||
|
||||
/**
|
||||
* Gets or sets the request timeout in milliseconds.
|
||||
@@ -132,6 +144,11 @@ export interface HttpContent {
|
||||
*/
|
||||
raw: any;
|
||||
|
||||
/**
|
||||
* Gets the response body as ArrayBuffer
|
||||
*/
|
||||
toArrayBuffer: () => ArrayBuffer;
|
||||
|
||||
/**
|
||||
* Gets the response body as string.
|
||||
*/
|
||||
|
||||
@@ -58,3 +58,17 @@ export function getFile(arg: any, destinationFilePath?: string): Promise<any> {
|
||||
}, e => reject(e));
|
||||
});
|
||||
}
|
||||
|
||||
export function getBinary(arg: any): Promise<ArrayBuffer> {
|
||||
return new Promise<ArrayBuffer>((resolve, reject) => {
|
||||
httpRequest.request(typeof arg === "string" ? { url: arg, method: "GET" } : arg)
|
||||
.then(r => {
|
||||
try {
|
||||
const arrayBuffer = r.content.toArrayBuffer();
|
||||
resolve(arrayBuffer);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
}, e => reject(e));
|
||||
});
|
||||
}
|
||||
|
||||
112
nativescript-core/text/text-common.ts
Normal file
112
nativescript-core/text/text-common.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
|
||||
const Object_prototype_toString = ({}).toString;
|
||||
const ArrayBufferString = Object_prototype_toString.call(ArrayBuffer.prototype);
|
||||
|
||||
function decoderReplacer(encoded) {
|
||||
var codePoint = encoded.charCodeAt(0) << 24;
|
||||
var leadingOnes = Math.clz32(~codePoint) | 0;
|
||||
var endPos = 0, stringLen = encoded.length | 0;
|
||||
var result = "";
|
||||
if (leadingOnes < 5 && stringLen >= leadingOnes) {
|
||||
codePoint = (codePoint << leadingOnes) >>> (24 + leadingOnes);
|
||||
for (endPos = 1; endPos < leadingOnes; endPos = endPos + 1 | 0) {
|
||||
codePoint = (codePoint << 6) | (encoded.charCodeAt(endPos) & 0x3f/*0b00111111*/);
|
||||
}
|
||||
if (codePoint <= 0xFFFF) { // BMP code point
|
||||
result += String.fromCharCode(codePoint);
|
||||
} else if (codePoint <= 0x10FFFF) {
|
||||
// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
|
||||
codePoint = codePoint - 0x10000 | 0;
|
||||
result += String.fromCharCode(
|
||||
(codePoint >> 10) + 0xD800 | 0, // highSurrogate
|
||||
(codePoint & 0x3ff) + 0xDC00 | 0 // lowSurrogate
|
||||
);
|
||||
} else { endPos = 0; } // to fill it in with INVALIDs
|
||||
}
|
||||
for (; endPos < stringLen; endPos = endPos + 1 | 0) { result += "\ufffd"; }
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function encoderReplacer(nonAsciiChars) {
|
||||
// make the UTF string into a binary UTF-8 encoded string
|
||||
var point = nonAsciiChars.charCodeAt(0) | 0;
|
||||
if (point >= 0xD800 && point <= 0xDBFF) {
|
||||
var nextcode = nonAsciiChars.charCodeAt(1) | 0;
|
||||
if (nextcode !== nextcode) { // NaN because string is 1 code point long
|
||||
return String.fromCharCode(0xef/*11101111*/, 0xbf/*10111111*/, 0xbd/*10111101*/);
|
||||
}
|
||||
// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
|
||||
if (nextcode >= 0xDC00 && nextcode <= 0xDFFF) {
|
||||
point = ((point - 0xD800) << 10) + nextcode - 0xDC00 + 0x10000 | 0;
|
||||
if (point > 0xffff) {
|
||||
return String.fromCharCode(
|
||||
(0x1e /*0b11110*/ << 3) | (point >>> 18),
|
||||
(0x2 /*0b10*/ << 6) | ((point >>> 12) & 0x3f/*0b00111111*/),
|
||||
(0x2 /*0b10*/ << 6) | ((point >>> 6) & 0x3f/*0b00111111*/),
|
||||
(0x2 /*0b10*/ << 6) | (point & 0x3f/*0b00111111*/)
|
||||
);
|
||||
}
|
||||
} else { return String.fromCharCode(0xef, 0xbf, 0xbd); }
|
||||
}
|
||||
if (point <= 0x007f) { return nonAsciiChars; }
|
||||
else if (point <= 0x07ff) {
|
||||
return String.fromCharCode((0x6 << 5) | (point >>> 6), (0x2 << 6) | (point & 0x3f));
|
||||
} else {
|
||||
return String.fromCharCode(
|
||||
(0xe /*0b1110*/ << 4) | (point >>> 12),
|
||||
(0x2 /*0b10*/ << 6) | ((point >>> 6) & 0x3f/*0b00111111*/),
|
||||
(0x2 /*0b10*/ << 6) | (point & 0x3f/*0b00111111*/)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class TextDecoder {
|
||||
public get encoding() {
|
||||
return "utf-8";
|
||||
}
|
||||
|
||||
public decode(input: BufferSource): string {
|
||||
const buffer = ArrayBuffer.isView(input) ? input.buffer : input;
|
||||
if (Object_prototype_toString.call(buffer) !== ArrayBufferString) {
|
||||
throw Error("Failed to execute 'decode' on 'TextDecoder': The provided value is not of type '(ArrayBuffer or ArrayBufferView)'");
|
||||
}
|
||||
let inputAs8 = new Uint8Array(buffer);
|
||||
let resultingString = "";
|
||||
for (let index = 0, len = inputAs8.length | 0; index < len; index = index + 32768 | 0) {
|
||||
resultingString += String.fromCharCode.apply(0, inputAs8.slice(index, index + 32768 | 0));
|
||||
}
|
||||
|
||||
return resultingString.replace(/[\xc0-\xff][\x80-\xbf]*/g, decoderReplacer);
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return "[object TextDecoder]";
|
||||
}
|
||||
|
||||
[Symbol.toStringTag] = "TextDecoder";
|
||||
}
|
||||
|
||||
export class TextEncoder {
|
||||
public get encoding() {
|
||||
return "utf-8";
|
||||
}
|
||||
|
||||
public encode(input: string = ""): Uint8Array {
|
||||
// 0xc0 => 0b11000000; 0xff => 0b11111111; 0xc0-0xff => 0b11xxxxxx
|
||||
// 0x80 => 0b10000000; 0xbf => 0b10111111; 0x80-0xbf => 0b10xxxxxx
|
||||
const encodedString = input === undefined ? "" : ("" + input).replace(/[\x80-\uD7ff\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]?/g, encoderReplacer);
|
||||
const len = encodedString.length | 0, result = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i = i + 1 | 0) {
|
||||
result[i] = encodedString.charCodeAt(i);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return "[object TextEncoder]";
|
||||
}
|
||||
|
||||
[Symbol.toStringTag] = "TextEncoder";
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
export module encoding {
|
||||
export * from "./text-common";
|
||||
|
||||
export module encoding {
|
||||
export const ISO_8859_1 = "ISO-8859-1";
|
||||
export const US_ASCII = "US-ASCII";
|
||||
export const UTF_16 = "UTF-16";
|
||||
|
||||
2
nativescript-core/text/text.d.ts
vendored
2
nativescript-core/text/text.d.ts
vendored
@@ -3,6 +3,8 @@
|
||||
* @module "text"
|
||||
*/ /** */
|
||||
|
||||
export * from "./text-common";
|
||||
|
||||
/**
|
||||
* Defines the supported character encodings.
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export module encoding {
|
||||
export * from "./text-common";
|
||||
|
||||
export module encoding {
|
||||
export const ISO_8859_1 = 5; //NSISOLatin1StringEncoding
|
||||
export const US_ASCII = 1; //NSASCIIStringEncoding
|
||||
export const UTF_16 = 10; //NSUnicodeStringEncoding
|
||||
|
||||
@@ -5,6 +5,8 @@ module XMLHttpRequestResponseType {
|
||||
export const empty = "";
|
||||
export const text = "text";
|
||||
export const json = "json";
|
||||
export const blob = "blob";
|
||||
export const arraybuffer = "arraybuffer";
|
||||
}
|
||||
|
||||
export class XMLHttpRequest {
|
||||
@@ -14,8 +16,12 @@ export class XMLHttpRequest {
|
||||
public LOADING = 3;
|
||||
public DONE = 4;
|
||||
|
||||
public onload: () => void;
|
||||
public onerror: (any) => void;
|
||||
public onabort: (...args: any[]) => void;
|
||||
public onerror: (...args: any[]) => void;
|
||||
public onload: (...args: any[]) => void;
|
||||
public onloadend: (...args: any[]) => void;
|
||||
public onloadstart: (...args: any[]) => void;
|
||||
public onprogress: (...args: any[]) => void;
|
||||
|
||||
private _options: http.HttpRequestOptions;
|
||||
private _readyState: number;
|
||||
@@ -24,14 +30,168 @@ export class XMLHttpRequest {
|
||||
private _responseTextReader: Function;
|
||||
private _headers: any;
|
||||
private _errorFlag: boolean;
|
||||
private _sendFlag: boolean;
|
||||
private _responseType: string = "";
|
||||
private _overrideMimeType: string;
|
||||
|
||||
private _listeners: Map<string, Array<Function>> = new Map<string, Array<Function>>();
|
||||
|
||||
public onreadystatechange: Function;
|
||||
|
||||
public get upload() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public get readyState(): number {
|
||||
return this._readyState;
|
||||
}
|
||||
|
||||
public get responseType(): string {
|
||||
return this._responseType;
|
||||
}
|
||||
|
||||
public set responseType(value: string) {
|
||||
if (value === XMLHttpRequestResponseType.empty
|
||||
|| value in XMLHttpRequestResponseType) {
|
||||
this._responseType = value;
|
||||
} else {
|
||||
throw new Error(`Response type of '${value}' not supported.`);
|
||||
}
|
||||
}
|
||||
|
||||
public get responseText(): string {
|
||||
if (this._responseType !== XMLHttpRequestResponseType.empty
|
||||
&& this._responseType !== XMLHttpRequestResponseType.text) {
|
||||
throw new Error(
|
||||
"Failed to read the 'responseText' property from 'XMLHttpRequest': " +
|
||||
"The value is only accessible if the object's 'responseType' is '' or 'text' " +
|
||||
`(was '${this._responseType}').`
|
||||
);
|
||||
}
|
||||
|
||||
if (types.isFunction(this._responseTextReader)) {
|
||||
return this._responseTextReader();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public get response(): any {
|
||||
if (this._responseType === XMLHttpRequestResponseType.empty
|
||||
|| this._responseType === XMLHttpRequestResponseType.text) {
|
||||
if (this._readyState !== this.LOADING && this._readyState !== this.DONE) {
|
||||
return "";
|
||||
} else {
|
||||
return this._response;
|
||||
}
|
||||
} else {
|
||||
if (this._readyState !== this.DONE) {
|
||||
return null;
|
||||
} else {
|
||||
return this._response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get status(): number {
|
||||
return this._status;
|
||||
}
|
||||
|
||||
public get statusText(): string {
|
||||
if (this._readyState === this.UNSENT
|
||||
|| this._readyState === this.OPENED
|
||||
|| this._errorFlag) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return statuses[this._status];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this._readyState = this.UNSENT;
|
||||
}
|
||||
|
||||
private _loadResponse(r: http.HttpResponse) {
|
||||
this._status = r.statusCode;
|
||||
this._headers = r.headers;
|
||||
this._setReadyState(this.HEADERS_RECEIVED);
|
||||
|
||||
this._setReadyState(this.LOADING);
|
||||
|
||||
this._responseTextReader = () => r.content.toString();
|
||||
|
||||
const contentType = this.getResponseHeader("Content-Type");
|
||||
const mimeType = (contentType && contentType.toLowerCase()) || "text/xml";
|
||||
const finalMimeType = this._overrideMimeType || mimeType;
|
||||
|
||||
if (this._responseType === XMLHttpRequestResponseType.json) {
|
||||
this._response = r.content.toJSON();
|
||||
} else if (this._responseType === XMLHttpRequestResponseType.text
|
||||
|| this._responseType === XMLHttpRequestResponseType.empty) {
|
||||
this._response = this.responseText;
|
||||
} else if (this._responseType === XMLHttpRequestResponseType.arraybuffer) {
|
||||
this._response = r.content.toArrayBuffer();
|
||||
} else if (this._responseType === XMLHttpRequestResponseType.blob) {
|
||||
this._response = new Blob([r.content.toArrayBuffer()], { type: finalMimeType });
|
||||
}
|
||||
|
||||
this.emitEvent("progress");
|
||||
|
||||
this._sendFlag = false;
|
||||
this._setReadyState(this.DONE);
|
||||
}
|
||||
|
||||
private emitEvent(eventName: string, ...args: Array<any>) {
|
||||
if (types.isFunction(this["on" + eventName])) {
|
||||
this["on" + eventName](...args);
|
||||
}
|
||||
|
||||
let handlers = this._listeners.get(eventName) || [];
|
||||
handlers.forEach((handler) => {
|
||||
handler(...args);
|
||||
});
|
||||
}
|
||||
|
||||
private _setReadyState(value: number) {
|
||||
if (this._readyState !== value) {
|
||||
this._readyState = value;
|
||||
this.emitEvent("readystatechange");
|
||||
}
|
||||
|
||||
if (this._readyState === this.DONE) {
|
||||
this.emitEvent("load");
|
||||
this.emitEvent("loadend");
|
||||
}
|
||||
}
|
||||
|
||||
private _setRequestError(eventName: string, error?: any) {
|
||||
this._readyState = this.DONE;
|
||||
|
||||
this._response = error;
|
||||
|
||||
this.emitEvent("readystatechange");
|
||||
|
||||
this.emitEvent(eventName, error);
|
||||
|
||||
this.emitEvent("loadend");
|
||||
}
|
||||
|
||||
public addEventListener(eventName: string, handler: Function) {
|
||||
if (["abort", "error", "load", "loadend", "loadstart", "progress"].indexOf(eventName) === -1) {
|
||||
throw new Error("Event not supported: " + eventName);
|
||||
}
|
||||
|
||||
let handlers = this._listeners.get(eventName) || [];
|
||||
handlers.push(handler);
|
||||
this._listeners.set(eventName, handlers);
|
||||
}
|
||||
|
||||
public removeEventListener(eventName: string, toDetach: Function) {
|
||||
let handlers = this._listeners.get(eventName) || [];
|
||||
handlers = handlers.filter((handler) => handler !== toDetach);
|
||||
this._listeners.set(eventName, handlers);
|
||||
}
|
||||
|
||||
public open(method: string, url: string, async?: boolean, user?: string, password?: string) {
|
||||
if (types.isString(method) && types.isString(url)) {
|
||||
this._options = { url: url, method: method };
|
||||
@@ -50,17 +210,21 @@ export class XMLHttpRequest {
|
||||
}
|
||||
|
||||
public abort() {
|
||||
this._errorFlag = true;
|
||||
|
||||
this._response = null;
|
||||
this._responseTextReader = null;
|
||||
this._headers = null;
|
||||
this._status = null;
|
||||
|
||||
if (this._readyState === this.UNSENT || this._readyState === this.OPENED || this._readyState === this.DONE) {
|
||||
if ((this._readyState === this.OPENED && this._sendFlag)
|
||||
|| this._readyState === this.HEADERS_RECEIVED
|
||||
|| this._readyState === this.LOADING) {
|
||||
this._errorFlag = true;
|
||||
this._sendFlag = false;
|
||||
this._setRequestError("abort");
|
||||
}
|
||||
|
||||
if (this._readyState === this.DONE) {
|
||||
this._readyState = this.UNSENT;
|
||||
} else {
|
||||
this._setReadyState(this.DONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,125 +235,51 @@ export class XMLHttpRequest {
|
||||
this._headers = null;
|
||||
this._status = null;
|
||||
|
||||
if (types.isDefined(this._options)) {
|
||||
if (types.isString(data) && this._options.method !== "GET") {
|
||||
//The Android Java HTTP lib throws an exception if we provide a
|
||||
//a request body for GET requests, so we avoid doing that.
|
||||
//Browser implementations silently ignore it as well.
|
||||
this._options.content = data;
|
||||
} else if (data instanceof FormData) {
|
||||
this._options.content = (<FormData>data).toString();
|
||||
if (this._readyState !== this.OPENED || this._sendFlag) {
|
||||
throw new Error(
|
||||
"Failed to execute 'send' on 'XMLHttpRequest': " +
|
||||
"The object's state must be OPENED."
|
||||
);
|
||||
}
|
||||
|
||||
if (types.isString(data) && this._options.method !== "GET") {
|
||||
//The Android Java HTTP lib throws an exception if we provide a
|
||||
//a request body for GET requests, so we avoid doing that.
|
||||
//Browser implementations silently ignore it as well.
|
||||
this._options.content = data;
|
||||
} else if (data instanceof FormData) {
|
||||
this._options.content = (<FormData>data).toString();
|
||||
} else if (data instanceof Blob) {
|
||||
this.setRequestHeader("Content-Type", data.type);
|
||||
this._options.content = Blob.InternalAccessor.getBuffer(data);
|
||||
} else if (data instanceof ArrayBuffer) {
|
||||
this._options.content = data;
|
||||
}
|
||||
|
||||
this._sendFlag = true;
|
||||
|
||||
this.emitEvent("loadstart");
|
||||
|
||||
http.request(this._options).then(r => {
|
||||
if (!this._errorFlag && this._sendFlag) {
|
||||
this._loadResponse(r);
|
||||
}
|
||||
|
||||
http.request(this._options).then(r => {
|
||||
if (!this._errorFlag) {
|
||||
this._loadResponse(r);
|
||||
}
|
||||
|
||||
}).catch(e => {
|
||||
this._errorFlag = true;
|
||||
this._setReadyState(this.DONE, e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _loadResponse(r) {
|
||||
this._status = r.statusCode;
|
||||
this._response = r.content.raw + "";
|
||||
this._headers = r.headers;
|
||||
this._setReadyState(this.HEADERS_RECEIVED);
|
||||
|
||||
this._setReadyState(this.LOADING);
|
||||
|
||||
this._setResponseType();
|
||||
|
||||
this._responseTextReader = () => r.content.toString();
|
||||
this._addToStringOnResponse();
|
||||
|
||||
if (this.responseType === XMLHttpRequestResponseType.json) {
|
||||
this._response = JSON.parse(this.responseText);
|
||||
} else if (this.responseType === XMLHttpRequestResponseType.text) {
|
||||
this._response = this.responseText;
|
||||
}
|
||||
|
||||
this._setReadyState(this.DONE);
|
||||
}
|
||||
|
||||
private _addToStringOnResponse() {
|
||||
// Add toString() method to ease debugging and
|
||||
// make Angular2 response.text() method work properly.
|
||||
if (types.isObject(this.response)) {
|
||||
Object.defineProperty(this._response, "toString", {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: () => this.responseText
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private textTypes: string[] = [
|
||||
"text/plain",
|
||||
"application/xml",
|
||||
"application/rss+xml",
|
||||
"text/html",
|
||||
"text/xml"
|
||||
];
|
||||
|
||||
private isTextContentType(contentType: string): boolean {
|
||||
let result = false;
|
||||
for (let i = 0; i < this.textTypes.length; i++) {
|
||||
if (contentType.toLowerCase().indexOf(this.textTypes[i]) >= 0) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private _setResponseType() {
|
||||
const header = this.getResponseHeader("Content-Type");
|
||||
const contentType = header && header.toLowerCase();
|
||||
|
||||
if (contentType) {
|
||||
if (contentType.indexOf("application/json") >= 0 || contentType.indexOf("+json") >= 0) {
|
||||
this.responseType = XMLHttpRequestResponseType.json;
|
||||
} else if (this.isTextContentType(contentType)) {
|
||||
this.responseType = XMLHttpRequestResponseType.text;
|
||||
}
|
||||
} else {
|
||||
this.responseType = XMLHttpRequestResponseType.text;
|
||||
}
|
||||
}
|
||||
|
||||
private _listeners: Map<string, Array<Function>> = new Map<string, Array<Function>>();
|
||||
|
||||
public addEventListener(eventName: string, handler: Function) {
|
||||
if (eventName !== "load" && eventName !== "error" && eventName !== "progress") {
|
||||
throw new Error("Event not supported: " + eventName);
|
||||
}
|
||||
|
||||
let handlers = this._listeners.get(eventName) || [];
|
||||
handlers.push(handler);
|
||||
this._listeners.set(eventName, handlers);
|
||||
}
|
||||
|
||||
public removeEventListener(eventName: string, toDetach: Function) {
|
||||
let handlers = this._listeners.get(eventName) || [];
|
||||
handlers = handlers.filter((handler) => handler !== toDetach);
|
||||
this._listeners.set(eventName, handlers);
|
||||
}
|
||||
|
||||
private emitEvent(eventName: string, ...args: Array<any>) {
|
||||
let handlers = this._listeners.get(eventName) || [];
|
||||
handlers.forEach((handler) => {
|
||||
handler(...args);
|
||||
}).catch(e => {
|
||||
this._errorFlag = true;
|
||||
this._sendFlag = false;
|
||||
this._setRequestError("error", e);
|
||||
});
|
||||
}
|
||||
|
||||
public setRequestHeader(header: string, value: string) {
|
||||
if (types.isDefined(this._options) && types.isString(header) && types.isString(value)) {
|
||||
if (this._readyState !== this.OPENED || this._sendFlag) {
|
||||
throw new Error(
|
||||
"Failed to execute 'setRequestHeader' on 'XMLHttpRequest': " +
|
||||
"The object's state must be OPENED."
|
||||
);
|
||||
}
|
||||
|
||||
if (types.isString(header) && types.isString(value)) {
|
||||
this._options.headers[header] = value;
|
||||
}
|
||||
}
|
||||
@@ -225,71 +315,14 @@ export class XMLHttpRequest {
|
||||
}
|
||||
|
||||
public overrideMimeType(mime: string) {
|
||||
//
|
||||
}
|
||||
|
||||
get readyState(): number {
|
||||
return this._readyState;
|
||||
}
|
||||
|
||||
public get responseType(): string {
|
||||
return this._responseType;
|
||||
}
|
||||
|
||||
public set responseType(value: string) {
|
||||
if (value === XMLHttpRequestResponseType.empty || value in XMLHttpRequestResponseType) {
|
||||
this._responseType = value;
|
||||
} else {
|
||||
throw new Error(`Response type of '${value}' not supported.`);
|
||||
}
|
||||
}
|
||||
|
||||
private _setReadyState(value: number, error?: any) {
|
||||
if (this._readyState !== value) {
|
||||
this._readyState = value;
|
||||
|
||||
if (types.isFunction(this.onreadystatechange)) {
|
||||
this.onreadystatechange();
|
||||
}
|
||||
if (this._readyState === this.LOADING || this._readyState === this.DONE) {
|
||||
throw new Error(
|
||||
"Failed to execute 'overrideMimeType' on 'XMLHttpRequest': " +
|
||||
"MimeType cannot be overridden when the state is LOADING or DONE."
|
||||
);
|
||||
}
|
||||
|
||||
if (this._readyState === this.DONE) {
|
||||
if (this._errorFlag) {
|
||||
if (types.isFunction(this.onerror)) {
|
||||
this.onerror(error);
|
||||
}
|
||||
this.emitEvent("error", error);
|
||||
} else {
|
||||
if (types.isFunction(this.onload)) {
|
||||
this.onload();
|
||||
}
|
||||
this.emitEvent("load");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get responseText(): string {
|
||||
if (types.isFunction(this._responseTextReader)) {
|
||||
return this._responseTextReader();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
get response(): any {
|
||||
return this._response;
|
||||
}
|
||||
|
||||
get status(): number {
|
||||
return this._status;
|
||||
}
|
||||
|
||||
get statusText(): string {
|
||||
if (this._readyState === this.UNSENT || this._readyState === this.OPENED || this._errorFlag) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return statuses[this._status];
|
||||
this._overrideMimeType = mime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,3 +390,257 @@ export class FormData {
|
||||
return arr.join("&");
|
||||
}
|
||||
}
|
||||
|
||||
export class Blob {
|
||||
// Note: only for use by XHR
|
||||
public static InternalAccessor = class {
|
||||
public static getBuffer(blob: Blob) {
|
||||
return blob._buffer;
|
||||
}
|
||||
};
|
||||
|
||||
private _buffer: Uint8Array;
|
||||
private _size: number;
|
||||
private _type: string;
|
||||
|
||||
public get size() {
|
||||
return this._size;
|
||||
}
|
||||
public get type() {
|
||||
return this._type;
|
||||
}
|
||||
|
||||
constructor(
|
||||
chunks: Array<BufferSource | DataView | Blob | string> = [],
|
||||
opts: { type?: string } = {}
|
||||
) {
|
||||
const dataChunks: Uint8Array[] = [];
|
||||
for (const chunk of chunks) {
|
||||
if (chunk instanceof Blob) {
|
||||
dataChunks.push(chunk._buffer);
|
||||
} else if (typeof chunk === "string") {
|
||||
const textEncoder = new TextEncoder();
|
||||
dataChunks.push(textEncoder.encode(chunk));
|
||||
} else if (chunk instanceof DataView) {
|
||||
dataChunks.push(new Uint8Array(chunk.buffer.slice(0)));
|
||||
} else if (chunk instanceof ArrayBuffer || ArrayBuffer.isView(chunk)) {
|
||||
dataChunks.push(new Uint8Array(
|
||||
ArrayBuffer.isView(chunk)
|
||||
? chunk.buffer.slice(0)
|
||||
: chunk.slice(0)
|
||||
));
|
||||
} else {
|
||||
const textEncoder = new TextEncoder();
|
||||
dataChunks.push(textEncoder.encode(String(chunk)));
|
||||
}
|
||||
}
|
||||
|
||||
const size = dataChunks.reduce((size, chunk) => size + chunk.byteLength, 0);
|
||||
const buffer = new Uint8Array(size);
|
||||
let offset = 0;
|
||||
for (let i = 0; i < dataChunks.length; i++) {
|
||||
const chunk = dataChunks[i];
|
||||
buffer.set(chunk, offset);
|
||||
offset += chunk.byteLength;
|
||||
}
|
||||
|
||||
this._buffer = buffer;
|
||||
this._size = this._buffer.byteLength;
|
||||
|
||||
this._type = opts.type || "";
|
||||
if (/[^\u0020-\u007E]/.test(this._type)) {
|
||||
this._type = "";
|
||||
} else {
|
||||
this._type = this._type.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
public arrayBuffer(): Promise<ArrayBuffer> {
|
||||
return Promise.resolve(this._buffer);
|
||||
}
|
||||
|
||||
public text(): Promise<string> {
|
||||
const textDecoder = new TextDecoder();
|
||||
|
||||
return Promise.resolve(textDecoder.decode(this._buffer));
|
||||
}
|
||||
|
||||
public slice(start?: number, end?: number, type?: string): Blob {
|
||||
const slice = this._buffer.slice(start || 0, end || this._buffer.length);
|
||||
|
||||
return new Blob([slice], { type: type });
|
||||
}
|
||||
|
||||
public stream() {
|
||||
throw new Error("stream is currently not supported");
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return "[object Blob]";
|
||||
}
|
||||
|
||||
[Symbol.toStringTag] = "Blob";
|
||||
}
|
||||
|
||||
export class File extends Blob {
|
||||
private _name: string;
|
||||
private _lastModified: number;
|
||||
|
||||
public get name() {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
public get lastModified() {
|
||||
return this._lastModified;
|
||||
}
|
||||
|
||||
constructor(
|
||||
chunks: Array<BufferSource | DataView | Blob | string>,
|
||||
name: string,
|
||||
opts: { type?: string, lastModified?: number } = {}
|
||||
) {
|
||||
super(chunks, opts);
|
||||
this._name = name.replace(/\//g, ":");
|
||||
this._lastModified =
|
||||
opts.lastModified
|
||||
? new Date(opts.lastModified).valueOf()
|
||||
: Date.now();
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return "[object File]";
|
||||
}
|
||||
|
||||
[Symbol.toStringTag] = "File";
|
||||
}
|
||||
|
||||
export class FileReader {
|
||||
public EMPTY = 0;
|
||||
public LOADING = 1;
|
||||
public DONE = 2;
|
||||
|
||||
public onabort: (...args: any[]) => void;
|
||||
public onerror: (...args: any[]) => void;
|
||||
public onload: (...args: any[]) => void;
|
||||
public onloadend: (...args: any[]) => void;
|
||||
public onloadstart: (...args: any[]) => void;
|
||||
public onprogress: (...args: any[]) => void;
|
||||
|
||||
private _readyState: number;
|
||||
private _result: string | ArrayBuffer | null;
|
||||
|
||||
private _listeners: Map<string, Array<Function>> = new Map<string, Array<Function>>();
|
||||
|
||||
public get readyState(): number {
|
||||
return this._readyState;
|
||||
}
|
||||
|
||||
public get result(): string | ArrayBuffer | null {
|
||||
return this._result;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
//
|
||||
}
|
||||
|
||||
private _array2base64(input: Uint8Array): string {
|
||||
var byteToCharMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
|
||||
var output = [];
|
||||
|
||||
for (var i = 0; i < input.length; i += 3) {
|
||||
var byte1 = input[i];
|
||||
var haveByte2 = i + 1 < input.length;
|
||||
var byte2 = haveByte2 ? input[i + 1] : 0;
|
||||
var haveByte3 = i + 2 < input.length;
|
||||
var byte3 = haveByte3 ? input[i + 2] : 0;
|
||||
|
||||
var outByte1 = byte1 >> 2;
|
||||
var outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4);
|
||||
var outByte3 = ((byte2 & 0x0F) << 2) | (byte3 >> 6);
|
||||
var outByte4 = byte3 & 0x3F;
|
||||
|
||||
if (!haveByte3) {
|
||||
outByte4 = 64;
|
||||
|
||||
if (!haveByte2) {
|
||||
outByte3 = 64;
|
||||
}
|
||||
}
|
||||
|
||||
output.push(
|
||||
byteToCharMap[outByte1], byteToCharMap[outByte2],
|
||||
byteToCharMap[outByte3], byteToCharMap[outByte4]
|
||||
);
|
||||
}
|
||||
|
||||
return output.join("");
|
||||
}
|
||||
|
||||
private _read(blob, kind) {
|
||||
if (!(blob instanceof Blob)) {
|
||||
throw new TypeError(`Failed to execute '${kind}' on 'FileReader': parameter 1 is not of type 'Blob'.`);
|
||||
}
|
||||
|
||||
this._result = "";
|
||||
|
||||
setTimeout(() => {
|
||||
this._readyState = this.LOADING;
|
||||
this.emitEvent("load");
|
||||
this.emitEvent("loadend");
|
||||
});
|
||||
}
|
||||
|
||||
private emitEvent(eventName: string, ...args: Array<any>) {
|
||||
if (types.isFunction(this["on" + eventName])) {
|
||||
this["on" + eventName](...args);
|
||||
}
|
||||
|
||||
let handlers = this._listeners.get(eventName) || [];
|
||||
handlers.forEach((handler) => {
|
||||
handler(...args);
|
||||
});
|
||||
}
|
||||
|
||||
public addEventListener(eventName: string, handler: Function) {
|
||||
if (["abort", "error", "load", "loadend", "loadstart", "progress"].indexOf(eventName) === -1) {
|
||||
throw new Error("Event not supported: " + eventName);
|
||||
}
|
||||
|
||||
let handlers = this._listeners.get(eventName) || [];
|
||||
handlers.push(handler);
|
||||
this._listeners.set(eventName, handlers);
|
||||
}
|
||||
|
||||
public removeEventListener(eventName: string, toDetach: Function) {
|
||||
let handlers = this._listeners.get(eventName) || [];
|
||||
handlers = handlers.filter((handler) => handler !== toDetach);
|
||||
this._listeners.set(eventName, handlers);
|
||||
}
|
||||
|
||||
public readAsDataURL(blob: Blob) {
|
||||
this._read(blob, "readAsDataURL");
|
||||
this._result = `data:${blob.type};base64,${this._array2base64(Blob.InternalAccessor.getBuffer(blob))}`;
|
||||
}
|
||||
|
||||
public readAsText(blob: Blob) {
|
||||
this._read(blob, "readAsText");
|
||||
const textDecoder = new TextDecoder();
|
||||
this._result = textDecoder.decode(Blob.InternalAccessor.getBuffer(blob));
|
||||
}
|
||||
|
||||
public readAsArrayBuffer(blob: Blob) {
|
||||
this._read(blob, "readAsArrayBuffer");
|
||||
this._result = Blob.InternalAccessor.getBuffer(blob).buffer.slice(0);
|
||||
}
|
||||
|
||||
public abort() {
|
||||
//
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return "[object FileReader]";
|
||||
}
|
||||
|
||||
[Symbol.toStringTag] = "FileReader";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user