browserfs
Version:
A filesystem in your browser!
225 lines (214 loc) • 6.89 kB
text/typescript
/**
* Contains utility methods for performing a variety of tasks with
* XmlHttpRequest across browsers.
*/
import {isIE, emptyBuffer} from '../core/util';
import {ApiError, ErrorCode} from '../core/api_error';
import {BFSCallback} from '../core/file_system';
/**
* @hidden
*/
function asyncDownloadFileModern(p: string, type: 'buffer', cb: BFSCallback<Buffer>): void;
function asyncDownloadFileModern(p: string, type: 'json', cb: BFSCallback<any>): void;
function asyncDownloadFileModern(p: string, type: string, cb: BFSCallback<any>): void;
function asyncDownloadFileModern(p: string, type: string, cb: BFSCallback<any>): void {
const req = new XMLHttpRequest();
req.open('GET', p, true);
let jsonSupported = true;
switch (type) {
case 'buffer':
req.responseType = 'arraybuffer';
break;
case 'json':
// Some browsers don't support the JSON response type.
// They either reset responseType, or throw an exception.
// @see https://github.com/Modernizr/Modernizr/blob/master/src/testXhrType.js
try {
req.responseType = 'json';
jsonSupported = req.responseType === 'json';
} catch (e) {
jsonSupported = false;
}
break;
default:
return cb(new ApiError(ErrorCode.EINVAL, "Invalid download type: " + type));
}
req.onreadystatechange = function(e) {
if (req.readyState === 4) {
if (req.status === 200) {
switch (type) {
case 'buffer':
// XXX: WebKit-based browsers return *null* when XHRing an empty file.
return cb(null, req.response ? Buffer.from(req.response) : emptyBuffer());
case 'json':
if (jsonSupported) {
return cb(null, req.response);
} else {
return cb(null, JSON.parse(req.responseText));
}
}
} else {
return cb(new ApiError(req.status, "XHR error."));
}
}
};
req.send();
}
/**
* @hidden
*/
function syncDownloadFileModern(p: string, type: 'buffer'): Buffer;
function syncDownloadFileModern(p: string, type: 'json'): any;
function syncDownloadFileModern(p: string, type: string): any;
function syncDownloadFileModern(p: string, type: string): any {
const req = new XMLHttpRequest();
req.open('GET', p, false);
// On most platforms, we cannot set the responseType of synchronous downloads.
// @todo Test for this; IE10 allows this, as do older versions of Chrome/FF.
let data: any = null;
let err: any = null;
// Classic hack to download binary data as a string.
req.overrideMimeType('text/plain; charset=x-user-defined');
req.onreadystatechange = function(e) {
if (req.readyState === 4) {
if (req.status === 200) {
switch (type) {
case 'buffer':
// Convert the text into a buffer.
const text = req.responseText;
data = Buffer.alloc(text.length);
// Throw away the upper bits of each character.
for (let i = 0; i < text.length; i++) {
// This will automatically throw away the upper bit of each
// character for us.
data[i] = text.charCodeAt(i);
}
return;
case 'json':
data = JSON.parse(req.responseText);
return;
}
} else {
err = new ApiError(req.status, "XHR error.");
return;
}
}
};
req.send();
if (err) {
throw err;
}
return data;
}
/**
* IE10 allows us to perform synchronous binary file downloads.
* @todo Feature detect this, as older versions of FF/Chrome do too!
* @hidden
*/
function syncDownloadFileIE10(p: string, type: 'buffer'): Buffer;
function syncDownloadFileIE10(p: string, type: 'json'): any;
function syncDownloadFileIE10(p: string, type: string): any;
function syncDownloadFileIE10(p: string, type: string): any {
const req = new XMLHttpRequest();
req.open('GET', p, false);
switch (type) {
case 'buffer':
req.responseType = 'arraybuffer';
break;
case 'json':
// IE10 does not support the JSON type.
break;
default:
throw new ApiError(ErrorCode.EINVAL, "Invalid download type: " + type);
}
let data: any;
let err: any;
req.onreadystatechange = function(e) {
if (req.readyState === 4) {
if (req.status === 200) {
switch (type) {
case 'buffer':
data = Buffer.from(req.response);
break;
case 'json':
data = JSON.parse(req.response);
break;
}
} else {
err = new ApiError(req.status, "XHR error.");
}
}
};
req.send();
if (err) {
throw err;
}
return data;
}
/**
* @hidden
*/
function getFileSize(async: boolean, p: string, cb: BFSCallback<number>): void {
const req = new XMLHttpRequest();
req.open('HEAD', p, async);
req.onreadystatechange = function(e) {
if (req.readyState === 4) {
if (req.status === 200) {
try {
return cb(null, parseInt(req.getResponseHeader('Content-Length') || '-1', 10));
} catch (e) {
// In the event that the header isn't present or there is an error...
return cb(new ApiError(ErrorCode.EIO, "XHR HEAD error: Could not read content-length."));
}
} else {
return cb(new ApiError(req.status, "XHR HEAD error."));
}
}
};
req.send();
}
/**
* Asynchronously download a file as a buffer or a JSON object.
* Note that the third function signature with a non-specialized type is
* invalid, but TypeScript requires it when you specialize string arguments to
* constants.
* @hidden
*/
export let asyncDownloadFile: {
(p: string, type: 'buffer', cb: BFSCallback<Buffer>): void;
(p: string, type: 'json', cb: BFSCallback<any>): void;
(p: string, type: string, cb: BFSCallback<any>): void;
} = asyncDownloadFileModern;
/**
* Synchronously download a file as a buffer or a JSON object.
* Note that the third function signature with a non-specialized type is
* invalid, but TypeScript requires it when you specialize string arguments to
* constants.
* @hidden
*/
export let syncDownloadFile: {
(p: string, type: 'buffer'): Buffer;
(p: string, type: 'json'): any;
(p: string, type: string): any;
} = (isIE && typeof Blob !== 'undefined') ? syncDownloadFileIE10 : syncDownloadFileModern;
/**
* Synchronously retrieves the size of the given file in bytes.
* @hidden
*/
export function getFileSizeSync(p: string): number {
let rv: number = -1;
getFileSize(false, p, function(err: ApiError, size?: number) {
if (err) {
throw err;
}
rv = size!;
});
return rv;
}
/**
* Asynchronously retrieves the size of the given file in bytes.
* @hidden
*/
export function getFileSizeAsync(p: string, cb: (err: ApiError, size?: number) => void): void {
getFileSize(true, p, cb);
}