UNPKG

browserfs

Version:

A filesystem in your browser!

225 lines (214 loc) 6.89 kB
/** * 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); }