Swapped out XMLHttpRequest for a custom module

We made a module to encapsulate the logic that chooses the nodejs or browser dependency that actually downloads a file when the module is used.
This commit is contained in:
Kieran Sedgwick 2014-05-19 16:47:24 -04:00
parent 5fcd313e2f
commit 9f33d8503e
7 changed files with 185 additions and 31 deletions

View File

@ -175,6 +175,21 @@ module.exports = function(grunt) {
remote: GIT_REMOTE, remote: GIT_REMOTE,
branch: 'gh-pages', branch: 'gh-pages',
force: true force: true
},
}
},
connect: {
server_for_node: {
options: {
port: 1234,
base: '.'
}
},
server_for_browser: {
options: {
port: 1234,
base: '.',
keepalive: true
} }
} }
} }
@ -191,6 +206,7 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-git'); grunt.loadNpmTasks('grunt-git');
grunt.loadNpmTasks('grunt-prompt'); grunt.loadNpmTasks('grunt-prompt');
grunt.loadNpmTasks('grunt-shell'); grunt.loadNpmTasks('grunt-shell');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.registerTask('develop', ['clean', 'requirejs:develop']); grunt.registerTask('develop', ['clean', 'requirejs:develop']);
grunt.registerTask('filer-test', ['clean', 'requirejs:test']); grunt.registerTask('filer-test', ['clean', 'requirejs:test']);
@ -214,7 +230,6 @@ module.exports = function(grunt) {
' to ' + semver.inc(currentVersion, patchLevel).yellow + '?'; ' to ' + semver.inc(currentVersion, patchLevel).yellow + '?';
grunt.config('prompt.confirm.options', promptOpts); grunt.config('prompt.confirm.options', promptOpts);
// TODO: ADD NPM RELEASE
grunt.task.run([ grunt.task.run([
'prompt:confirm', 'prompt:confirm',
'checkBranch', 'checkBranch',
@ -226,7 +241,9 @@ module.exports = function(grunt) {
'npm-publish' 'npm-publish'
]); ]);
}); });
grunt.registerTask('test', ['check', 'filer-test', 'shell:mocha']); grunt.registerTask('test-node', ['check', 'filer-test', 'connect:server_for_node', 'shell:mocha']);
grunt.registerTask('test-browser', ['check', 'filer-test', 'connect:server_for_browser']);
grunt.registerTask('test', ['test-node']);
grunt.registerTask('default', ['test']); grunt.registerTask('default', ['test']);
}; };

View File

@ -25,15 +25,17 @@
"url": "https://github.com/js-platform/filer.git" "url": "https://github.com/js-platform/filer.git"
}, },
"dependencies": { "dependencies": {
"bower": "~1.0.0" "bower": "~1.0.0",
"request": "^2.36.0"
}, },
"devDependencies": { "devDependencies": {
"chai": "~1.9.1",
"grunt": "~0.4.0", "grunt": "~0.4.0",
"grunt-bump": "0.0.13", "grunt-bump": "0.0.13",
"grunt-contrib-clean": "~0.4.0", "grunt-contrib-clean": "~0.4.0",
"grunt-contrib-compress": "~0.4.1", "grunt-contrib-compress": "~0.4.1",
"grunt-contrib-concat": "~0.1.3", "grunt-contrib-concat": "~0.1.3",
"grunt-contrib-connect": "~0.7.1", "grunt-contrib-connect": "^0.7.1",
"grunt-contrib-jshint": "~0.7.1", "grunt-contrib-jshint": "~0.7.1",
"grunt-contrib-requirejs": "~0.4.0", "grunt-contrib-requirejs": "~0.4.0",
"grunt-contrib-uglify": "~0.1.2", "grunt-contrib-uglify": "~0.1.2",
@ -47,6 +49,7 @@
"semver": "^2.3.0", "semver": "^2.3.0",
"requirejs": "~2.1.11", "requirejs": "~2.1.11",
"mocha": "~1.18.2", "mocha": "~1.18.2",
"chai": "~1.9.1" "requirejs": "~2.1.11"
} },
"main": "dist/filer_node.js"
} }

82
src/network.js Normal file
View File

@ -0,0 +1,82 @@
define(function(require) {
// Pull in node's request module if possible/needed
if (typeof XMLHttpRequest === 'undefined') {
// This is a stupid workaround for the fact that
// the r.js optimizer checks every require() call
// during optimization and throws an error if it
// can't find the module.
//
// This is only an issue with our browser build
// using `almond` (https://github.com/jrburke/almond)
// which doesn't fallback to node's require when we
// need it to.
var node_req = require;
var request = node_req('request');
}
function browserDownload(uri, callback) {
var query = new XMLHttpRequest();
query.onload = function() {
var err = query.status != 200 ? { message: query.statusText, code: query.status } : null,
data = err ? null : new Uint8Array(query.response);
callback(err, data);
};
query.open("GET", uri);
if("withCredentials" in query) {
query.withCredentials = true;
}
query.responseType = "arraybuffer";
query.send();
}
function nodeDownload(uri, callback) {
request({
url: uri,
method: "GET",
encoding: null
}, function(err, msg, body) {
var data = null,
arrayBuffer,
statusCode,
arrayLength = body && body.length,
error;
msg = msg || null;
statusCode = msg && msg.statusCode;
error = statusCode != 200 ? { message: err || 'Not found!', code: statusCode } : null;
arrayBuffer = arrayLength && new ArrayBuffer(arrayLength);
// Convert buffer to Uint8Array
if (arrayBuffer && (statusCode == 200)) {
data = new Uint8Array(arrayBuffer);
for (var i = 0; i < body.length; ++i) {
data[i] = body[i];
}
}
callback(error, data);
});
}
return {
download: function(uri, callback) {
if (!uri) {
throw('Uri required!');
}
if (!callback) {
throw('Callback required');
}
if (typeof XMLHttpRequest === "undefined") {
nodeDownload(uri, callback);
} else {
browserDownload(uri, callback);
}
}
};
});

View File

@ -5,6 +5,7 @@ define(function(require) {
var Errors = require('src/errors'); var Errors = require('src/errors');
var Environment = require('src/shell/environment'); var Environment = require('src/shell/environment');
var async = require('async'); var async = require('async');
var Network = require('src/network');
require('zip'); require('zip');
require('unzip'); require('unzip');
@ -447,20 +448,18 @@ define(function(require) {
// Grab whatever is after the last / (assuming there is one) and // Grab whatever is after the last / (assuming there is one) and
// remove any non-filename type chars(i.e., : and /). Like the real // remove any non-filename type chars(i.e., : and /). Like the real
// wget, we leave query string or hash portions in tact. // wget, we leave query string or hash portions in tact.
var path = options.filename || url.replace(/[:/]/g, '').split('/').pop(); var path = options.filename || url.replace(/[:/]/, '').split('/').pop();
path = Path.resolve(fs.cwd, path); path = Path.resolve(fs.cwd, path);
function onerror() { function onerror() {
callback(new Error('unable to get resource')); callback(new Error('unable to get resource'));
} }
var request = new XMLHttpRequest(); Network.download('get', url, function(err, data) {
request.onload = function() { if (err || !data) {
if(request.status !== 200) {
return onerror(); return onerror();
} }
var data = new Uint8Array(request.response);
fs.writeFile(path, data, function(err) { fs.writeFile(path, data, function(err) {
if(err) { if(err) {
callback(err); callback(err);
@ -468,15 +467,7 @@ define(function(require) {
callback(null, path); callback(null, path);
} }
}); });
};
request.onerror = onerror;
request.open("GET", url, true);
if("withCredentials" in request) {
request.withCredentials = true;
} }
request.responseType = "arraybuffer";
request.send();
}; };
Shell.prototype.unzip = function(zipfile, options, callback) { Shell.prototype.unzip = function(zipfile, options, callback) {

View File

@ -7,7 +7,8 @@
// //
// ?filer-dist/filer.js --> use dist/filer.js // ?filer-dist/filer.js --> use dist/filer.js
// ?filer-dist/filer.min.js --> use dist/filer.min.js // ?filer-dist/filer.min.js --> use dist/filer.min.js
// ?<default> --> (default) use src/filer.js with require // ?filer-src/filer.js --> use src/filer.js with require
// ?<default> --> (default) use dist/filer-test.js with require
var filerArgs = window.filerArgs = {}; var filerArgs = window.filerArgs = {};
var config = (function() { var config = (function() {
var query = window.location.search.substring(1); var query = window.location.search.substring(1);
@ -51,24 +52,39 @@ var config = (function() {
} }
// Support src/ filer via require // Support src/ filer via require
if(filerArgs['filer-src/filer.js']) {
return {
paths: {
"tests": "../tests",
"src": "../src",
"spec": "../tests/spec",
"bugs": "../tests/bugs",
"util": "../tests/lib/test-utils",
"Filer": "../src/index"
},
baseUrl: "../lib",
optimize: "none",
shim: {
// TextEncoder and TextDecoder shims. encoding-indexes must get loaded first,
// and we use a fake one for reduced size, since we only care about utf8.
"encoding": {
deps: ["encoding-indexes-shim"]
}
}
};
}
// Support dist/filer-test.js
return { return {
paths: { paths: {
"tests": "../tests", "tests": "../tests",
"src": "../src",
"spec": "../tests/spec", "spec": "../tests/spec",
"bugs": "../tests/bugs", "bugs": "../tests/bugs",
"util": "../tests/lib/test-utils", "util": "../tests/lib/test-utils",
"Filer": "../src/index" "Filer": "../dist/filer-test"
}, },
baseUrl: "../lib", baseUrl: "../lib",
optimize: "none", optimize: "none"
shim: {
// TextEncoder and TextDecoder shims. encoding-indexes must get loaded first,
// and we use a fake one for reduced size, since we only care about utf8.
"encoding": {
deps: ["encoding-indexes-shim"]
}
}
}; };
}()); }());

44
tests/spec/lib.spec.js Normal file
View File

@ -0,0 +1,44 @@
define(['../../src/network'], function(network) {
describe('Network download tool', function() {
if(typeof XMLHttpRequest === "undefined") {
it('should connect to server',function(done) {
var request = require('request');
request({
url: 'http://localhost:8080/package.json',
method: 'GET',
encoding: 'utf-8'
}, function(error, msg, body) {
expect(error).not.to.exist;
done();
});
});
}
it('should throw an exception when a URI is not passed', function(done) {
expect(function() {
network.download(undefined, function(error, data, statusCode) {});
}).to.throwError;
done();
});
it('should get an error when a non-existent path is specified', function(done) {
network.download('http://localhost:8080/file-non-existent', function(error, data, statusCode) {
expect(error).not.to.exist;
expect(statusCode).to.eql('404');
expect(data).to.be.a(null);
done();
});
});
it('should download a resource from the server', function(done) {
network.download('http://localhost:8080/package.json', function(error, data, statusCode) {
expect(error).not.to.exist;
expect(statusCode).to.eql('200');
expect(data).to.exist;
expect(data).to.have.length.above(0);
done();
});
});
});
});

View File

@ -38,6 +38,7 @@ define([
"spec/time-flags.spec", "spec/time-flags.spec",
"spec/fs.watch.spec", "spec/fs.watch.spec",
"spec/errors.spec", "spec/errors.spec",
"spec/lib.spec",
// Filer.FileSystem.providers.* // Filer.FileSystem.providers.*
"spec/providers/providers.spec", "spec/providers/providers.spec",