// Released under the MIT/X11 license // http://www.opensource.org/licenses/mit-license.php "use strict"; var Request = require("request").Request; var xhrMod = require("xhr"); // Initialize myStorage if we don't have it. var myStorage = require("simple-storage").storage; if (!myStorage.cachedRequest) { myStorage.cachedRequest = {}; } // https://bugzilla.mozilla.org/582760 is still alive var debugOption = true; function debug(str) { if (debugOption) { console.log(str); } } /** * Decorating cached response object with a bit of additional * functionality. */ function CachedResponse (url) { if (!myStorage.cachedRequest[url]) { myStorage.cachedRequest[url] = {}; this.url = url; this.text = null; this.status = ""; this.statusText = ""; this.headers = {}; } else { var storedResponse = myStorage.cachedRequest[url]; this.url = url; this.text = storedResponse.text; this.status = storedResponse.status; this.statusText = storedResponse.statusText; this.headers = storedResponse.headers; } } /** * getter returning object from this.text * (so we don't save essentially the same object same) * * @return Object from this.text, if this.text isn't JSONable, * return null */ CachedResponse.prototype.__defineGetter__("json", function() { try { return JSON.parse(this.text); } catch (ex) { if (ex instanceof SyntaxError) { return null; } else { throw ex; } } }); /** * save the current object's values to myStorage. */ CachedResponse.prototype.saveCached = function() { var storedResponse = myStorage.cachedRequest[this.url]; storedResponse.text = this.text; storedResponse.status = this.status; storedResponse.statusText = this.statusText; storedResponse.headers = this.headers; }; /** * getter returning Last-Modified header as a Date object. * * @return Date when this has Last-Modified header, or null otherwise */ CachedResponse.prototype.__defineGetter__("lastModified", function() { if (this.headers && (this.headers.hasOwnProperty("Last-Modified"))) { return new Date(this.headers["Last-Modified"]); } return null; }); /** * Emulates Request object, but caches the result for speedier access, * and protection when the remote site is down. * * @opts Object with properties same as Request * * Limitations: does only GET requests, so it doesn't even have * .post(), .get() or any other methods. * Contrary to Request() opts can have onError callback, and * getExpiredAnyway property to allow using expired cached value, * if the remote connection returns error. */ exports.CachedRequest = function CachedRequest(opts) { var crStorage = new CachedResponse(opts.url); var req = new xhrMod.XMLHttpRequest(); req.open("HEAD", opts.url, true); req.onreadystatechange = function () { if (req.readyState == 4) { if(req.status == 200) { var curETag = req.getResponseHeader("ETag"); var curLastModified = new Date(req.getResponseHeader("Last-Modified")); if (crStorage && crStorage.headers && ((curETag == crStorage.headers.eTag) || (curLastModified <= crStorage.lastModified))) { debug("Loading from cache!"); // use cached values } else { // cache is not up-to-date new Request({ url: opts.url, onComplete: function(resp) { if (resp.status == 200) { debug("Too old cache! Reloaded"); crStorage.headers = resp.headers; crStorage.text = resp.text; crStorage.status = resp.status; crStorage.statusText = resp.statusText; crStorage.saveCached(); } else { // If we cannot get response from the real URL, // we may be happy with getting our fix from // anywhere, including (possibly) expired cache if (opts.getExpiredAnyway && crStorage && crStorage.text) { debug("Nothing better to do! Returning expired cache."); } else { // We definitively lost, just call .onComplete // with what we have. opts.onComplete({ text: resp.text, json: null, status: resp.status, statusText: resp.statusText, headers: resp.headers }); // to avoid call opts.onComplete below return ; } } } }).get(); } opts.onComplete(crStorage); } } }; req.send(); }; //vim: set ts=2 et sw=2 textwidth=80: