|
| 1 | +function CachedXMLHttpRequest() { |
| 2 | + var self = this, xhr = new CachedXMLHttpRequest.XMLHttpRequest(), cache = {}; |
| 3 | + |
| 4 | + function send() { |
| 5 | + var onload = xhr.onload; |
| 6 | + xhr.onload = function (e) { |
| 7 | + var meta = { |
| 8 | + requestURL: cache.requestURL, |
| 9 | + responseURL: xhr.responseURL, |
| 10 | + responseType: xhr.responseType, |
| 11 | + lastModified: xhr.getResponseHeader("Last-Modified"), |
| 12 | + eTag: xhr.getResponseHeader("ETag"), |
| 13 | + }; |
| 14 | + if (xhr.status == 200 && (meta.lastModified || meta.eTag)) { |
| 15 | + meta.size = xhr.response.byteLength; |
| 16 | + CachedXMLHttpRequest.cache.put(cache.requestURL, meta, xhr.response, function (err) { |
| 17 | + CachedXMLHttpRequest.log("'" + cache.requestURL + "' downloaded successfully (" + xhr.response.byteLength + " bytes) " + |
| 18 | + (err ? "but not stored in indexedDB cache due to error." : "and stored in indexedDB cache.")); |
| 19 | + if (onload) |
| 20 | + onload(e); |
| 21 | + }); |
| 22 | + } else { |
| 23 | + if (xhr.status == 304) { |
| 24 | + cache.override = true; |
| 25 | + CachedXMLHttpRequest.log("'" + cache.requestURL + "' served from indexedDB cache (" + cache.response.byteLength + " bytes)."); |
| 26 | + } |
| 27 | + if (onload) |
| 28 | + onload(e); |
| 29 | + } |
| 30 | + } |
| 31 | + return xhr.send.apply(xhr, arguments); |
| 32 | + } |
| 33 | + |
| 34 | + function revalidateCrossOriginRequest(meta, self, sendArguments) { |
| 35 | + var headXHR = new CachedXMLHttpRequest.XMLHttpRequest(); |
| 36 | + headXHR.open("HEAD", meta.requestURL, false); |
| 37 | + headXHR.send(); |
| 38 | + cache.override = meta.lastModified ? meta.lastModified == headXHR.getResponseHeader("Last-Modified") : meta.eTag && meta.eTag == headXHR.getResponseHeader("ETag"); |
| 39 | + if (!cache.override) |
| 40 | + return send.apply(self, sendArguments); |
| 41 | + CachedXMLHttpRequest.log("'" + cache.requestURL + "' served from indexedDB cache (" + cache.response.byteLength + " bytes)."); |
| 42 | + if (xhr.onload) |
| 43 | + xhr.onload(); |
| 44 | + } |
| 45 | + |
| 46 | + Object.defineProperty(self, "open", { value: function (method, url, async) { |
| 47 | + cache = { method: method, requestURL: CachedXMLHttpRequest.cache.requestURL(url), async: async }; |
| 48 | + return xhr.open.apply(xhr, arguments); |
| 49 | + }}); |
| 50 | + |
| 51 | + Object.defineProperty(self, "setRequestHeader", { value: function () { |
| 52 | + cache.customHeaders = true; |
| 53 | + return xhr.setRequestHeader.apply(xhr, arguments); |
| 54 | + }}); |
| 55 | + |
| 56 | + Object.defineProperty(self, "send", { value: function (data) { |
| 57 | + var sendArguments = arguments; |
| 58 | + var absoluteUrlMatch = cache.requestURL.match("^https?:\/\/[^\/]+\/"); |
| 59 | + if (!absoluteUrlMatch || cache.customHeaders || data || cache.method != "GET" || !cache.async || xhr.responseType != "arraybuffer") |
| 60 | + return xhr.send.apply(xhr, sendArguments); |
| 61 | + CachedXMLHttpRequest.cache.get(cache.requestURL, function (err, result) { |
| 62 | + if (err || !result || !result.meta || result.meta.responseType != xhr.responseType) |
| 63 | + return send.apply(self, sendArguments); |
| 64 | + cache.status = 200; |
| 65 | + cache.statusText = "OK"; |
| 66 | + cache.response = result.response; |
| 67 | + cache.responseURL = result.meta.responseURL; |
| 68 | + if (window.location.href.lastIndexOf(absoluteUrlMatch[0], 0)) |
| 69 | + return revalidateCrossOriginRequest(result.meta, self, sendArguments); |
| 70 | + if (result.meta.lastModified) |
| 71 | + xhr.setRequestHeader("If-Modified-Since", result.meta.lastModified); |
| 72 | + else if (result.meta.eTag) |
| 73 | + xhr.setRequestHeader("If-None-Match", result.meta.eTag); |
| 74 | + xhr.setRequestHeader("Cache-Control", "no-cache"); |
| 75 | + return send.apply(self, sendArguments); |
| 76 | + }); |
| 77 | + }}); |
| 78 | + |
| 79 | + ["abort", "getAllResponseHeaders", "getResponseHeader", "overrideMimeType", "addEventListener"].forEach(function (method) { |
| 80 | + Object.defineProperty(self, method, { value: function () { return xhr[method].apply(xhr, arguments); } }); |
| 81 | + }); |
| 82 | + |
| 83 | + ["readyState", "response", "responseText", "responseType", "responseURL", "responseXML", "status", "statusText", "timeout", "upload", "withCredentials", |
| 84 | + "onloadstart", "onprogress", "onabort", "onerror", "onload", "ontimeout", "onloadend", "onreadystatechange"].forEach(function (property) { |
| 85 | + Object.defineProperty(self, property, { |
| 86 | + get: function () { return (cache.override && cache[property]) ? cache[property] : xhr[property]; }, |
| 87 | + set: function (value) { xhr[property] = value; }, |
| 88 | + }); |
| 89 | + }); |
| 90 | + |
| 91 | +} |
| 92 | + |
| 93 | +CachedXMLHttpRequest.XMLHttpRequest = window.XMLHttpRequest; |
| 94 | + |
| 95 | +CachedXMLHttpRequest.log = function (message) { |
| 96 | + if (Module.CachedXMLHttpRequestSilent !== true) |
| 97 | + console.log("[CachedXMLHttpRequest] " + message); |
| 98 | +}; |
| 99 | + |
| 100 | +CachedXMLHttpRequest.cache = { |
| 101 | + database: "CachedXMLHttpRequest", |
| 102 | + version: 1, |
| 103 | + store: "cache", |
| 104 | + indexedDB: window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB, |
| 105 | + link: document.createElement("a"), |
| 106 | + requestURL: function (url) { |
| 107 | + this.link.href = url; |
| 108 | + return this.link.href; |
| 109 | + |
| 110 | + }, |
| 111 | + id: function (requestURL) { |
| 112 | + return encodeURIComponent(requestURL); |
| 113 | + |
| 114 | + }, |
| 115 | + queue: [], |
| 116 | + processQueue: function () { |
| 117 | + var self = this; |
| 118 | + self.queue.forEach(function (queued) { self[queued.action].apply(self, queued.arguments) }); |
| 119 | + self.queue = []; |
| 120 | + |
| 121 | + }, |
| 122 | + init: function () { |
| 123 | + var self = this; |
| 124 | + if (!self.indexedDB) |
| 125 | + return CachedXMLHttpRequest.log("indexedDB is not available"); |
| 126 | + var openDB; |
| 127 | + try { |
| 128 | + openDB = indexedDB.open(self.database, self.version); |
| 129 | + } catch(e) { |
| 130 | + return CachedXMLHttpRequest.log("indexedDB access denied"); |
| 131 | + } |
| 132 | + openDB.onupgradeneeded = function (e) { |
| 133 | + var db = e.target.result; |
| 134 | + var transaction = e.target.transaction; |
| 135 | + var objectStore; |
| 136 | + if (db.objectStoreNames.contains(self.store)) { |
| 137 | + objectStore = transaction.objectStore(self.store); |
| 138 | + } else { |
| 139 | + objectStore = db.createObjectStore(self.store, {keyPath: "id"}); |
| 140 | + objectStore.createIndex("meta", "meta", {unique: false}); |
| 141 | + } |
| 142 | + objectStore.clear(); |
| 143 | + } |
| 144 | + openDB.onerror = function (e) { |
| 145 | + CachedXMLHttpRequest.log("can not open indexedDB database"); |
| 146 | + self.indexedDB = null; |
| 147 | + self.processQueue(); |
| 148 | + } |
| 149 | + openDB.onsuccess = function (e) { |
| 150 | + self.db = e.target.result; |
| 151 | + self.processQueue(); |
| 152 | + } |
| 153 | + |
| 154 | + }, |
| 155 | + put: function (requestURL, meta, response, callback) { |
| 156 | + var self = this; |
| 157 | + if (!self.indexedDB) |
| 158 | + return callback(new Error("indexedDB is not available")); |
| 159 | + if (!self.db) |
| 160 | + return self.queue.push({action: "put", arguments: arguments}); |
| 161 | + meta.version = self.version; |
| 162 | + var putDB = self.db.transaction([self.store], "readwrite").objectStore(self.store).put({id: self.id(requestURL), meta: meta, response: response}); |
| 163 | + putDB.onerror = function (e) { callback(new Error("failed to put request into indexedDB cache")); } |
| 164 | + putDB.onsuccess = function (e) { callback(null); } |
| 165 | + |
| 166 | + }, |
| 167 | + get: function (requestURL, callback) { |
| 168 | + var self = this; |
| 169 | + if (!self.indexedDB) |
| 170 | + return callback(new Error("indexedDB is not available")); |
| 171 | + if (!self.db) |
| 172 | + return self.queue.push({action: "get", arguments: arguments}); |
| 173 | + var getDB = self.db.transaction([self.store], "readonly").objectStore(self.store).get(self.id(requestURL)); |
| 174 | + getDB.onerror = function (e) { callback(new Error("failed to get request from indexedDB cache")); } |
| 175 | + getDB.onsuccess = function (e) { callback(null, e.target.result); } |
| 176 | + |
| 177 | + }, |
| 178 | +}; |
| 179 | + |
| 180 | +CachedXMLHttpRequest.cache.init(); |
| 181 | + |
| 182 | +CachedXMLHttpRequest.wrap = function (func) { |
| 183 | + return function () { |
| 184 | + var realXMLHttpRequest = XMLHttpRequest; |
| 185 | + XMLHttpRequest = CachedXMLHttpRequest; |
| 186 | + try { |
| 187 | + var result = func.apply(this, arguments); |
| 188 | + } catch (e) { |
| 189 | + XMLHttpRequest = realXMLHttpRequest; |
| 190 | + throw e; |
| 191 | + } |
| 192 | + XMLHttpRequest = realXMLHttpRequest; |
| 193 | + return result; |
| 194 | + }; |
| 195 | +}; |
| 196 | + |
| 197 | +if (Module.CachedXMLHttpRequestDisable !== true) { |
| 198 | + if (Module.CachedXMLHttpRequestLoader === true) { |
| 199 | + if (typeof LoadCompressedFile == "function") |
| 200 | + LoadCompressedFile = CachedXMLHttpRequest.wrap(LoadCompressedFile); |
| 201 | + if (typeof DecompressAndLoadFile == "function") |
| 202 | + DecompressAndLoadFile = CachedXMLHttpRequest.wrap(DecompressAndLoadFile); |
| 203 | + } |
| 204 | + Object.defineProperty(Module, "asmLibraryArg", { |
| 205 | + get: function () { return Module.realAsmLibraryArg; }, |
| 206 | + set: function (value) { |
| 207 | + if (typeof value == "object" && typeof value._JS_WebRequest_Create == "function") |
| 208 | + value._JS_WebRequest_Create = CachedXMLHttpRequest.wrap(value._JS_WebRequest_Create); |
| 209 | + Module.realAsmLibraryArg = value; |
| 210 | + }, |
| 211 | + }); |
| 212 | +} |
0 commit comments