tube
Version:
Experimental Resource Loader implementing using Server Sent Events
112 lines (94 loc) • 3.6 kB
JavaScript
var async = require('async'),
debug = require('debug')('tube'),
fs = require('fs'),
path = require('path'),
http = require('http'),
fstream = require('fstream'),
Manifest = require('../src/manifest'),
reLineBreak = /\r?\n/g,
defaultAssetPath = path.resolve(path.dirname(module.parent.filename), 'assets');
function loadManifest(req, opts, callback) {
var manifestLocation = opts.manifest || defaultAssetPath,
manifest = new Manifest();
console.log(req.url);
// stat the manifest
fs.stat(manifestLocation, function(err, stats) {
if (err) return callback(err);
// if we have a directory, then load the files from the directory
if (stats.isDirectory()) {
fstream.Reader({ path: manifestLocation })
.on('child', function(file) {
if (file.type === 'File') {
manifest.add({
path: file.path,
size: file.size
});
}
})
.on('end', function() {
callback(null, manifest);
});
}
else {
return callback();
}
});
}
function loadManifestContent(manifest, callback) {
async.map(manifest.files, fs.readFile, function(err, results) {
if (err) return callback(err);
// iterate through the results and update the manifest resources
results.forEach(function(buffer, index) {
manifest.resources[index].data = buffer.toString();
});
callback(null, manifest);
});
}
function reportError(err, res) {
res.write('event: exception\n');
res.write('data: ' + err.message + '\n\n');
return res.end();
}
function supplyAsset(res, separator) {
// ensure we have a separator (default to 0x214B - ⅋)
separator = separator || String.fromCharCode(0x214B);
return function(resource, callback) {
debug('attempting to load asset: ' + resource.path);
fs.readFile(resource.path, 'utf8', function(err, data) {
if (err) return callback(err);
res.write('event: resource\n');
res.write('data: ' + JSON.stringify({
data: data.split(reLineBreak).join(separator)
}) + '\n\n');
});
};
}
module.exports = function(opts) {
// ensure we have opts
opts = opts || {};
return function(req, res) {
loadManifest(req, opts, function(err, manifest) {
if (err) {
debug('received error: ' + err);
}
req.once('end', function() {
console.log('request aborted');
res.end();
});
// if we hit an error, then provide a 204 which tells the EventSource to stop
// attempting to reconnect
// see: http://www.w3.org/TR/eventsource/#processing-model
res.writeHead(err ? 204 : 200, {
"content-type":"text/event-stream",
"Cache-Control":"no-cache",
"Connection":"keep-alive"
});
if (err) return reportError(err, res);
async.forEach(manifest.resources, supplyAsset(res, opts.separator), function(err) {
return reportError(err, res);
res.write('event: done\n\n');
res.end();
});
});
};
};