UNPKG

browserfs

Version:

A filesystem in your browser!

280 lines (264 loc) 9.53 kB
/** * Contains utility methods for performing a variety of tasks with * XmlHttpRequest across browsers. */ import util = require('../core/util'); import {ApiError, ErrorCode} from '../core/api_error'; // See core/polyfills for the VBScript definition of these functions. declare var IEBinaryToArray_ByteStr: (vbarr: any) => string; declare var IEBinaryToArray_ByteStr_Last: (vbarr: any) => string; // Converts 'responseBody' in IE into the equivalent 'responseText' that other // browsers would generate. function getIEByteArray(IEByteArray: any): number[] { var rawBytes = IEBinaryToArray_ByteStr(IEByteArray); var lastChr = IEBinaryToArray_ByteStr_Last(IEByteArray); var data_str = rawBytes.replace(/[\s\S]/g, function(match) { var v = match.charCodeAt(0) return String.fromCharCode(v&0xff, v>>8) }) + lastChr; var data_array = new Array(data_str.length); for (var i = 0; i < data_str.length; i++) { data_array[i] = data_str.charCodeAt(i); } return data_array; } function downloadFileIE(async: boolean, p: string, type: string, cb: (err: ApiError, data?: any) => void): void { switch(type) { case 'buffer': // Fallthrough case 'json': break; default: return cb(new ApiError(ErrorCode.EINVAL, "Invalid download type: " + type)); } var req = new XMLHttpRequest(); req.open('GET', p, async); req.setRequestHeader("Accept-Charset", "x-user-defined"); req.onreadystatechange = function(e) { var data_array; if (req.readyState === 4) { if (req.status === 200) { switch(type) { case 'buffer': data_array = getIEByteArray(req.responseBody); return cb(null, new Buffer(data_array)); case 'json': return cb(null, JSON.parse(req.responseText)); } } else { return cb(new ApiError(req.status, "XHR error.")); } } }; req.send(); } function asyncDownloadFileIE(p: string, type: 'buffer', cb: (err: ApiError, data?: NodeBuffer) => void): void; function asyncDownloadFileIE(p: string, type: 'json', cb: (err: ApiError, data?: any) => void): void; function asyncDownloadFileIE(p: string, type: string, cb: (err: ApiError, data?: any) => void): void; function asyncDownloadFileIE(p: string, type: string, cb: (err: ApiError, data?: any) => void): void { downloadFileIE(true, p, type, cb); } function syncDownloadFileIE(p: string, type: 'buffer'): NodeBuffer; function syncDownloadFileIE(p: string, type: 'json'): any; function syncDownloadFileIE(p: string, type: string): any; function syncDownloadFileIE(p: string, type: string): any { var rv; downloadFileIE(false, p, type, function(err: ApiError, data?: any) { if (err) throw err; rv = data; }); return rv; } function asyncDownloadFileModern(p: string, type: 'buffer', cb: (err: ApiError, data?: NodeBuffer) => void): void; function asyncDownloadFileModern(p: string, type: 'json', cb: (err: ApiError, data?: any) => void): void; function asyncDownloadFileModern(p: string, type: string, cb: (err: ApiError, data?: any) => void): void; function asyncDownloadFileModern(p: string, type: string, cb: (err: ApiError, data?: any) => void): void { var req = new XMLHttpRequest(); req.open('GET', p, true); var 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, new Buffer(req.response ? req.response : 0)); 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(); } function syncDownloadFileModern(p: string, type: 'buffer'): NodeBuffer; function syncDownloadFileModern(p: string, type: 'json'): any; function syncDownloadFileModern(p: string, type: string): any; function syncDownloadFileModern(p: string, type: string): any { var 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. var data = null; var err = 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. var text = req.responseText; data = new Buffer(text.length); // Throw away the upper bits of each character. for (var i = 0; i < text.length; i++) { // This will automatically throw away the upper bit of each // character for us. data.writeUInt8(text.charCodeAt(i), 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! */ function syncDownloadFileIE10(p: string, type: 'buffer'): NodeBuffer; function syncDownloadFileIE10(p: string, type: 'json'): any; function syncDownloadFileIE10(p: string, type: string): any; function syncDownloadFileIE10(p: string, type: string): any { var 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); } var data; var err; req.onreadystatechange = function(e) { if (req.readyState === 4) { if (req.status === 200) { switch(type) { case 'buffer': data = new Buffer(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; } function getFileSize(async: boolean, p: string, cb: (err: ApiError, size?: number) => void): void { var 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. */ export var asyncDownloadFile: { (p: string, type: 'buffer', cb: (err: ApiError, data?: NodeBuffer) => void): void; (p: string, type: 'json', cb: (err: ApiError, data?: any) => void): void; (p: string, type: string, cb: (err: ApiError, data?: any) => void): void; } = (util.isIE && typeof Blob === 'undefined') ? asyncDownloadFileIE : 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. */ export var syncDownloadFile: { (p: string, type: 'buffer'): NodeBuffer; (p: string, type: 'json'): any; (p: string, type: string): any; } = (util.isIE && typeof Blob === 'undefined') ? syncDownloadFileIE : (util.isIE && typeof Blob !== 'undefined') ? syncDownloadFileIE10 : syncDownloadFileModern; /** * Synchronously retrieves the size of the given file in bytes. */ export function getFileSizeSync(p: string): number { var rv: number; 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. */ export function getFileSizeAsync(p: string, cb: (err: ApiError, size?: number) => void): void { getFileSize(true, p, cb); }