mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
As reported by @leosco: https://github.com/meteor/meteor/pull/9942#issuecomment-411902381
189 lines
5.6 KiB
JavaScript
189 lines
5.6 KiB
JavaScript
var dbPromise;
|
|
|
|
var canUseCache =
|
|
// The server doesn't benefit from dynamic module fetching, and almost
|
|
// certainly doesn't support IndexedDB.
|
|
Meteor.isClient &&
|
|
// Cordova bundles all modules into the monolithic initial bundle, so
|
|
// the dynamic module cache won't be necessary.
|
|
! Meteor.isCordova &&
|
|
// Caching can be confusing in development, and is designed to be a
|
|
// transparent optimization for production performance.
|
|
Meteor.isProduction;
|
|
|
|
function getIDB() {
|
|
if (typeof indexedDB !== "undefined") return indexedDB;
|
|
if (typeof webkitIndexedDB !== "undefined") return webkitIndexedDB;
|
|
if (typeof mozIndexedDB !== "undefined") return mozIndexedDB;
|
|
if (typeof OIndexedDB !== "undefined") return OIndexedDB;
|
|
if (typeof msIndexedDB !== "undefined") return msIndexedDB;
|
|
}
|
|
|
|
function withDB(callback) {
|
|
dbPromise = dbPromise || new Promise(function (resolve, reject) {
|
|
var idb = getIDB();
|
|
if (! idb) {
|
|
throw new Error("IndexedDB not available");
|
|
}
|
|
|
|
// Incrementing the version number causes all existing object stores
|
|
// to be deleted and recreates those specified by objectStoreMap.
|
|
var request = idb.open("MeteorDynamicImportCache", 2);
|
|
|
|
request.onupgradeneeded = function (event) {
|
|
var db = event.target.result;
|
|
|
|
// It's fine to delete existing object stores since onupgradeneeded
|
|
// is only called when we change the DB version number, and the data
|
|
// we're storing is disposable/reconstructible.
|
|
Array.from(db.objectStoreNames).forEach(db.deleteObjectStore, db);
|
|
|
|
Object.keys(objectStoreMap).forEach(function (name) {
|
|
db.createObjectStore(name, objectStoreMap[name]);
|
|
});
|
|
};
|
|
|
|
request.onerror = makeOnError(reject, "indexedDB.open");
|
|
request.onsuccess = function (event) {
|
|
resolve(event.target.result);
|
|
};
|
|
});
|
|
|
|
return dbPromise.then(callback, function (error) {
|
|
return callback(null);
|
|
});
|
|
}
|
|
|
|
var objectStoreMap = {
|
|
sourcesByVersion: { keyPath: "version" }
|
|
};
|
|
|
|
function makeOnError(reject, source) {
|
|
return function (event) {
|
|
reject(new Error(
|
|
"IndexedDB failure in " + source + " " +
|
|
JSON.stringify(event.target)
|
|
));
|
|
|
|
// Returning true from an onerror callback function prevents an
|
|
// InvalidStateError in Firefox during Private Browsing. Silencing
|
|
// that error is safe because we handle the error more gracefully by
|
|
// passing it to the Promise reject function above.
|
|
// https://github.com/meteor/meteor/issues/8697
|
|
return true;
|
|
};
|
|
}
|
|
|
|
var checkCount = 0;
|
|
|
|
exports.checkMany = function (versions) {
|
|
var ids = Object.keys(versions);
|
|
var sourcesById = Object.create(null);
|
|
|
|
// Initialize sourcesById with null values to indicate all sources are
|
|
// missing (unless replaced with actual sources below).
|
|
ids.forEach(function (id) {
|
|
sourcesById[id] = null;
|
|
});
|
|
|
|
if (! canUseCache) {
|
|
return Promise.resolve(sourcesById);
|
|
}
|
|
|
|
return withDB(function (db) {
|
|
if (! db) {
|
|
// We thought we could used IndexedDB, but something went wrong
|
|
// while opening the database, so err on the side of safety.
|
|
return sourcesById;
|
|
}
|
|
|
|
var txn = db.transaction([
|
|
"sourcesByVersion"
|
|
], "readonly");
|
|
|
|
var sourcesByVersion = txn.objectStore("sourcesByVersion");
|
|
|
|
++checkCount;
|
|
|
|
function finish() {
|
|
--checkCount;
|
|
return sourcesById;
|
|
}
|
|
|
|
return Promise.all(ids.map(function (id) {
|
|
return new Promise(function (resolve, reject) {
|
|
var version = versions[id];
|
|
if (version) {
|
|
var sourceRequest = sourcesByVersion.get(version);
|
|
sourceRequest.onerror = makeOnError(reject, "sourcesByVersion.get");
|
|
sourceRequest.onsuccess = function (event) {
|
|
var result = event.target.result;
|
|
if (result) {
|
|
sourcesById[id] = result.source;
|
|
}
|
|
resolve();
|
|
};
|
|
} else resolve();
|
|
});
|
|
})).then(finish, finish);
|
|
});
|
|
};
|
|
|
|
var pendingVersionsAndSourcesById = Object.create(null);
|
|
|
|
exports.setMany = function (versionsAndSourcesById) {
|
|
if (canUseCache) {
|
|
Object.assign(
|
|
pendingVersionsAndSourcesById,
|
|
versionsAndSourcesById
|
|
);
|
|
|
|
// Delay the call to flushSetMany so that it doesn't contribute to the
|
|
// amount of time it takes to call module.dynamicImport.
|
|
if (! flushSetMany.timer) {
|
|
flushSetMany.timer = setTimeout(flushSetMany, 100);
|
|
}
|
|
}
|
|
};
|
|
|
|
function flushSetMany() {
|
|
if (checkCount > 0) {
|
|
// If checkMany is currently underway, postpone the flush until later,
|
|
// since updating the cache is less important than reading from it.
|
|
return flushSetMany.timer = setTimeout(flushSetMany, 100);
|
|
}
|
|
|
|
flushSetMany.timer = null;
|
|
|
|
var versionsAndSourcesById = pendingVersionsAndSourcesById;
|
|
pendingVersionsAndSourcesById = Object.create(null);
|
|
|
|
return withDB(function (db) {
|
|
if (! db) {
|
|
// We thought we could used IndexedDB, but something went wrong
|
|
// while opening the database, so err on the side of safety.
|
|
return;
|
|
}
|
|
|
|
var setTxn = db.transaction([
|
|
"sourcesByVersion"
|
|
], "readwrite");
|
|
|
|
var sourcesByVersion = setTxn.objectStore("sourcesByVersion");
|
|
|
|
return Promise.all(
|
|
Object.keys(versionsAndSourcesById).map(function (id) {
|
|
var info = versionsAndSourcesById[id];
|
|
return new Promise(function (resolve, reject) {
|
|
var request = sourcesByVersion.put({
|
|
version: info.version,
|
|
source: info.source
|
|
});
|
|
request.onerror = makeOnError(reject, "sourcesByVersion.put");
|
|
request.onsuccess = resolve;
|
|
});
|
|
})
|
|
);
|
|
});
|
|
}
|