skypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmail
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
959 lines
39 KiB
959 lines
39 KiB
9 years ago
|
// here, the extra check for window['Ext'] is needed for use with cmd-test
|
||
|
// code injection. we need to make that this file will sync up with page global
|
||
|
// scope to avoid duplicate Ext.Boot state. That check is after the initial Ext check
|
||
|
// to allow the sandboxing template to inject an appropriate Ext var and prevent the
|
||
|
// global detection.
|
||
|
var Ext = Ext || window['Ext'] || {};
|
||
|
|
||
|
|
||
|
//<editor-fold desc="Microloader">
|
||
|
/**
|
||
|
* @class Ext.Microloader
|
||
|
* @private
|
||
|
* @singleton
|
||
|
*/
|
||
|
Ext.Microloader = Ext.Microloader || (function () {
|
||
|
var Boot = Ext.Boot,
|
||
|
//<debug>
|
||
|
_debug = function (message) {
|
||
|
//console.log(message);
|
||
|
},
|
||
|
//</debug>
|
||
|
_warn = function (message) {
|
||
|
console.log("[WARN] " + message);
|
||
|
},
|
||
|
_privatePrefix = '_ext:' + location.pathname,
|
||
|
|
||
|
/**
|
||
|
* @method getStorageKey
|
||
|
* The Following combination is used to create isolated local storage keys
|
||
|
* '_ext' is used to scope all the local storage keys that we internally by Ext
|
||
|
* 'location.pathname' is used to force each assets to cache by an absolute URL (/build/MyApp) (dev vs prod)
|
||
|
* 'url' is used to force each asset to cache relative to the page (app.json vs resources/app.css)
|
||
|
* 'profileId' is used to differentiate the builds of an application (neptune vs crisp)
|
||
|
* 'Microloader.appId' is unique to the application and will differentiate apps on the same host (dev mode running app watch against multiple apps)
|
||
|
*/
|
||
|
getStorageKey = function(url, profileId) {
|
||
|
return _privatePrefix + url + '-' + (profileId ? profileId + '-' : '') + Microloader.appId;
|
||
|
},
|
||
|
postProcessor, _storage;
|
||
|
|
||
|
try {
|
||
|
_storage = window['localStorage'];
|
||
|
} catch(ex) {
|
||
|
// ignore
|
||
|
}
|
||
|
|
||
|
var _cache = window['applicationCache'],
|
||
|
// Local Storage Controller
|
||
|
LocalStorage = {
|
||
|
clearAllPrivate: function(manifest) {
|
||
|
if(_storage) {
|
||
|
|
||
|
//Remove the entry for the manifest first
|
||
|
_storage.removeItem(manifest.key);
|
||
|
|
||
|
var i, key,
|
||
|
removeKeys = [],
|
||
|
suffix = manifest.profile + '-' + Microloader.appId,
|
||
|
ln = _storage.length;
|
||
|
for (i = 0; i < ln; i++) {
|
||
|
key = _storage.key(i);
|
||
|
// If key starts with the private key and the suffix is present we can clear this entry
|
||
|
if (key.indexOf(_privatePrefix) === 0 && key.indexOf(suffix) !== -1) {
|
||
|
removeKeys.push(key);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for(i in removeKeys) {
|
||
|
//<debug>
|
||
|
_debug("Removing "+ removeKeys[i] + " from Local Storage");
|
||
|
//</debug>
|
||
|
_storage.removeItem(removeKeys[i]);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
retrieveAsset: function (key) {
|
||
|
try {
|
||
|
return _storage.getItem(key);
|
||
|
}
|
||
|
catch (e) {
|
||
|
// Private browsing mode
|
||
|
return null;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
setAsset: function(key, content) {
|
||
|
try {
|
||
|
if (content === null || content == '') {
|
||
|
_storage.removeItem(key);
|
||
|
} else {
|
||
|
_storage.setItem(key, content);
|
||
|
}
|
||
|
}
|
||
|
catch (e) {
|
||
|
if (_storage && e.code == e.QUOTA_EXCEEDED_ERR) {
|
||
|
//<debug>
|
||
|
_warn("LocalStorage Quota exceeded, cannot store " + key + " locally");
|
||
|
//</debug>
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var Asset = function (cfg) {
|
||
|
if (typeof cfg.assetConfig === 'string') {
|
||
|
this.assetConfig = {
|
||
|
path: cfg.assetConfig
|
||
|
};
|
||
|
} else {
|
||
|
this.assetConfig = cfg.assetConfig;
|
||
|
}
|
||
|
|
||
|
this.type = cfg.type;
|
||
|
this.key = getStorageKey(this.assetConfig.path, cfg.manifest.profile);
|
||
|
|
||
|
if (cfg.loadFromCache) {
|
||
|
this.loadFromCache();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Asset.prototype = {
|
||
|
shouldCache: function() {
|
||
|
return _storage && this.assetConfig.update && this.assetConfig.hash && !this.assetConfig.remote;
|
||
|
},
|
||
|
|
||
|
is: function (asset) {
|
||
|
return (!!asset && this.assetConfig && asset.assetConfig && (this.assetConfig.hash === asset.assetConfig.hash))
|
||
|
},
|
||
|
|
||
|
cache: function(content) {
|
||
|
if (this.shouldCache()) {
|
||
|
LocalStorage.setAsset(this.key, content || this.content);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
uncache: function() {
|
||
|
LocalStorage.setAsset(this.key, null);
|
||
|
},
|
||
|
|
||
|
updateContent: function (content) {
|
||
|
this.content = content;
|
||
|
},
|
||
|
|
||
|
getSize: function () {
|
||
|
return this.content ? this.content.length : 0;
|
||
|
},
|
||
|
|
||
|
loadFromCache: function() {
|
||
|
if (this.shouldCache()) {
|
||
|
this.content = LocalStorage.retrieveAsset(this.key);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var Manifest = function (cfg) {
|
||
|
if (typeof cfg.content === "string") {
|
||
|
this.content = JSON.parse(cfg.content);
|
||
|
} else {
|
||
|
this.content = cfg.content;
|
||
|
}
|
||
|
this.assetMap = {};
|
||
|
|
||
|
this.url = cfg.url;
|
||
|
this.fromCache = !!cfg.cached;
|
||
|
this.assetCache = !(cfg.assetCache === false);
|
||
|
this.key = getStorageKey(this.url);
|
||
|
|
||
|
// Pull out select properties for repetitive use
|
||
|
this.profile = this.content.profile;
|
||
|
this.hash = this.content.hash;
|
||
|
this.loadOrder = this.content.loadOrder;
|
||
|
this.deltas = this.content.cache ? this.content.cache.deltas : null;
|
||
|
this.cacheEnabled = this.content.cache ? this.content.cache.enable : false;
|
||
|
|
||
|
this.loadOrderMap = (this.loadOrder) ? Boot.createLoadOrderMap(this.loadOrder) : null;
|
||
|
|
||
|
var tags = this.content.tags,
|
||
|
platformTags = Ext.platformTags;
|
||
|
|
||
|
if (tags) {
|
||
|
if (tags instanceof Array) {
|
||
|
for (var i = 0; i < tags.length; i++) {
|
||
|
platformTags[tags[i]] = true;
|
||
|
}
|
||
|
} else {
|
||
|
Boot.apply(platformTags, tags);
|
||
|
}
|
||
|
|
||
|
// re-apply the query parameters, so that the params as specified
|
||
|
// in the url always has highest priority
|
||
|
Boot.apply(platformTags, Boot.loadPlatformsParam());
|
||
|
}
|
||
|
|
||
|
// Convert all assets into Assets
|
||
|
this.js = this.processAssets(this.content.js, 'js');
|
||
|
this.css = this.processAssets(this.content.css, 'css');
|
||
|
};
|
||
|
|
||
|
Manifest.prototype = {
|
||
|
processAsset: function(assetConfig, type) {
|
||
|
var processedAsset = new Asset({
|
||
|
manifest: this,
|
||
|
assetConfig: assetConfig,
|
||
|
type: type,
|
||
|
loadFromCache: this.assetCache
|
||
|
});
|
||
|
this.assetMap[assetConfig.path] = processedAsset;
|
||
|
return processedAsset;
|
||
|
},
|
||
|
|
||
|
processAssets: function(assets, type) {
|
||
|
var results = [],
|
||
|
ln = assets.length,
|
||
|
i, assetConfig;
|
||
|
|
||
|
for (i = 0; i < ln; i++) {
|
||
|
assetConfig = assets[i];
|
||
|
results.push(this.processAsset(assetConfig, type));
|
||
|
}
|
||
|
|
||
|
return results;
|
||
|
},
|
||
|
|
||
|
useAppCache: function() {
|
||
|
return true;
|
||
|
},
|
||
|
|
||
|
// Concatenate all assets for easy access
|
||
|
getAssets: function () {
|
||
|
return this.css.concat(this.js);
|
||
|
},
|
||
|
|
||
|
getAsset: function (path) {
|
||
|
return this.assetMap[path];
|
||
|
},
|
||
|
|
||
|
shouldCache: function() {
|
||
|
return this.hash && this.cacheEnabled;
|
||
|
},
|
||
|
|
||
|
cache: function(content) {
|
||
|
if (this.shouldCache()) {
|
||
|
LocalStorage.setAsset(this.key, JSON.stringify(content || this.content));
|
||
|
}
|
||
|
//<debug>
|
||
|
else {
|
||
|
_debug("Manifest caching is disabled.");
|
||
|
}
|
||
|
//</debug>
|
||
|
},
|
||
|
|
||
|
is: function(manifest) {
|
||
|
//<debug>
|
||
|
_debug("Testing Manifest: " + this.hash + " VS " + manifest.hash);
|
||
|
//</debug>
|
||
|
return this.hash === manifest.hash;
|
||
|
},
|
||
|
|
||
|
// Clear the manifest from local storage
|
||
|
uncache: function() {
|
||
|
LocalStorage.setAsset(this.key, null);
|
||
|
},
|
||
|
|
||
|
exportContent: function() {
|
||
|
return Boot.apply({
|
||
|
loadOrderMap: this.loadOrderMap
|
||
|
}, this.content);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Microloader
|
||
|
* @type {Array}
|
||
|
* @private
|
||
|
*/
|
||
|
var _listeners = [],
|
||
|
_loaded = false,
|
||
|
Microloader = {
|
||
|
init: function () {
|
||
|
Ext.microloaded = true;
|
||
|
|
||
|
// data-app is in the dev template for an application and is also
|
||
|
// injected into the app my CMD for production
|
||
|
// We use this to prefix localStorage cache to prevent collisions
|
||
|
var microloaderElement = document.getElementById('microloader');
|
||
|
Microloader.appId = microloaderElement ? microloaderElement.getAttribute('data-app') : '';
|
||
|
|
||
|
if (Ext.beforeLoad) {
|
||
|
postProcessor = Ext.beforeLoad(Ext.platformTags);
|
||
|
}
|
||
|
|
||
|
var readyHandler = Ext._beforereadyhandler;
|
||
|
|
||
|
Ext._beforereadyhandler = function () {
|
||
|
if (Ext.Boot !== Boot) {
|
||
|
Ext.apply(Ext.Boot, Boot);
|
||
|
Ext.Boot = Boot;
|
||
|
}
|
||
|
if (readyHandler) {
|
||
|
readyHandler();
|
||
|
}
|
||
|
};
|
||
|
},
|
||
|
|
||
|
applyCacheBuster: function(url) {
|
||
|
var tstamp = new Date().getTime(),
|
||
|
sep = url.indexOf('?') === -1 ? '?' : '&';
|
||
|
url = url + sep + "_dc=" + tstamp;
|
||
|
return url;
|
||
|
},
|
||
|
|
||
|
run: function() {
|
||
|
Microloader.init();
|
||
|
var manifest = Ext.manifest;
|
||
|
|
||
|
if (typeof manifest === "string") {
|
||
|
var extension = ".json",
|
||
|
url = manifest.indexOf(extension) === manifest.length - extension.length
|
||
|
? manifest
|
||
|
: manifest + ".json",
|
||
|
key = getStorageKey(url),
|
||
|
content = LocalStorage.retrieveAsset(key);
|
||
|
|
||
|
// Manifest found in local storage, use this for immediate boot except in PhantomJS environments for building.
|
||
|
if (content) {
|
||
|
//<debug>
|
||
|
_debug("Manifest file, '" + url + "', was found in Local Storage");
|
||
|
//</debug>
|
||
|
manifest = new Manifest({
|
||
|
url: url,
|
||
|
content: content,
|
||
|
cached: true
|
||
|
});
|
||
|
if (postProcessor) {
|
||
|
postProcessor(manifest);
|
||
|
}
|
||
|
Microloader.load(manifest);
|
||
|
|
||
|
|
||
|
// Manifest is not in local storage. Fetch it from the server
|
||
|
} else {
|
||
|
Boot.fetch(Microloader.applyCacheBuster(url), function (result) {
|
||
|
//<debug>
|
||
|
_debug("Manifest file was not found in Local Storage, loading: " + url);
|
||
|
//</debug>
|
||
|
manifest = new Manifest({
|
||
|
url: url,
|
||
|
content: result.content
|
||
|
});
|
||
|
|
||
|
manifest.cache();
|
||
|
if (postProcessor) {
|
||
|
postProcessor(manifest);
|
||
|
}
|
||
|
Microloader.load(manifest);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Embedded Manifest into JS file
|
||
|
} else {
|
||
|
//<debug>
|
||
|
_debug("Manifest was embedded into application javascript file");
|
||
|
//</debug>
|
||
|
manifest = new Manifest({
|
||
|
content: manifest
|
||
|
});
|
||
|
Microloader.load(manifest);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @param {Manifest} manifest
|
||
|
*/
|
||
|
load: function (manifest) {
|
||
|
Microloader.urls = [];
|
||
|
Microloader.manifest = manifest;
|
||
|
Ext.manifest = Microloader.manifest.exportContent();
|
||
|
|
||
|
var assets = manifest.getAssets(),
|
||
|
cachedAssets = [],
|
||
|
asset, i, len, include, entry;
|
||
|
|
||
|
for (len = assets.length, i = 0; i < len; i++) {
|
||
|
asset = assets[i];
|
||
|
include = Microloader.filterAsset(asset);
|
||
|
if (include) {
|
||
|
// Asset is using the localStorage caching system
|
||
|
if (manifest.shouldCache() && asset.shouldCache()) {
|
||
|
// Asset already has content from localStorage, instantly seed that into boot
|
||
|
if (asset.content) {
|
||
|
//<debug>
|
||
|
_debug("Asset: " + asset.assetConfig.path + " was found in local storage. No remote load for this file");
|
||
|
//</debug>
|
||
|
entry = Boot.registerContent(asset.assetConfig.path, asset.type, asset.content);
|
||
|
if (entry.evaluated) {
|
||
|
_warn("Asset: " + asset.assetConfig.path + " was evaluated prior to local storage being consulted.");
|
||
|
}
|
||
|
//load via AJAX and seed content into Boot
|
||
|
} else {
|
||
|
//<debug>
|
||
|
_debug("Asset: " + asset.assetConfig.path + " was NOT found in local storage. Adding to load queue");
|
||
|
//</debug>
|
||
|
cachedAssets.push(asset);
|
||
|
}
|
||
|
}
|
||
|
Microloader.urls.push(asset.assetConfig.path);
|
||
|
Boot.assetConfig[asset.assetConfig.path] = Boot.apply({type: asset.type}, asset.assetConfig);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If any assets are using the caching system and do not have local versions load them first via AJAX
|
||
|
if (cachedAssets.length > 0) {
|
||
|
Microloader.remainingCachedAssets = cachedAssets.length;
|
||
|
while (cachedAssets.length > 0) {
|
||
|
asset = cachedAssets.pop();
|
||
|
//<debug>
|
||
|
_debug("Preloading/Fetching Cached Assets from: " + asset.assetConfig.path);
|
||
|
//</debug>
|
||
|
Boot.fetch(asset.assetConfig.path, (function(asset) {
|
||
|
return function(result) {
|
||
|
Microloader.onCachedAssetLoaded(asset, result);
|
||
|
}
|
||
|
})(asset));
|
||
|
}
|
||
|
} else {
|
||
|
Microloader.onCachedAssetsReady();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// Load the asset and seed its content into Boot to be evaluated in sequence
|
||
|
onCachedAssetLoaded: function (asset, result) {
|
||
|
var checksum;
|
||
|
result = Microloader.parseResult(result);
|
||
|
Microloader.remainingCachedAssets--;
|
||
|
|
||
|
if (!result.error) {
|
||
|
checksum = Microloader.checksum(result.content, asset.assetConfig.hash);
|
||
|
if (!checksum) {
|
||
|
_warn("Cached Asset '" + asset.assetConfig.path + "' has failed checksum. This asset will be uncached for future loading");
|
||
|
|
||
|
// Un cache this asset so it is loaded next time
|
||
|
asset.uncache();
|
||
|
}
|
||
|
|
||
|
//<debug>
|
||
|
_debug("Checksum for Cached Asset: " + asset.assetConfig.path + " is " + checksum);
|
||
|
//</debug>
|
||
|
Boot.registerContent(asset.assetConfig.path, asset.type, result.content);
|
||
|
asset.updateContent(result.content);
|
||
|
asset.cache();
|
||
|
} else {
|
||
|
_warn("There was an error pre-loading the asset '" + asset.assetConfig.path + "'. This asset will be uncached for future loading");
|
||
|
|
||
|
// Un cache this asset so it is loaded next time
|
||
|
asset.uncache();
|
||
|
}
|
||
|
|
||
|
if (Microloader.remainingCachedAssets === 0) {
|
||
|
Microloader.onCachedAssetsReady();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
onCachedAssetsReady: function(){
|
||
|
Boot.load({
|
||
|
url: Microloader.urls,
|
||
|
loadOrder: Microloader.manifest.loadOrder,
|
||
|
loadOrderMap: Microloader.manifest.loadOrderMap,
|
||
|
sequential: true,
|
||
|
success: Microloader.onAllAssetsReady,
|
||
|
failure: Microloader.onAllAssetsReady
|
||
|
});
|
||
|
},
|
||
|
|
||
|
onAllAssetsReady: function() {
|
||
|
_loaded = true;
|
||
|
Microloader.notify();
|
||
|
|
||
|
if (navigator.onLine !== false) {
|
||
|
//<debug>
|
||
|
_debug("Application is online, checking for updates");
|
||
|
//</debug>
|
||
|
Microloader.checkAllUpdates();
|
||
|
}
|
||
|
else {
|
||
|
//<debug>
|
||
|
_debug("Application is offline, adding online listener to check for updates");
|
||
|
//</debug>
|
||
|
if(window['addEventListener']) {
|
||
|
window.addEventListener('online', Microloader.checkAllUpdates, false);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
onMicroloaderReady: function (listener) {
|
||
|
if (_loaded) {
|
||
|
listener();
|
||
|
} else {
|
||
|
_listeners.push(listener);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @private
|
||
|
*/
|
||
|
notify: function () {
|
||
|
//<debug>
|
||
|
_debug("notifying microloader ready listeners.");
|
||
|
//</debug>
|
||
|
var listener;
|
||
|
while((listener = _listeners.shift())) {
|
||
|
listener();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// Delta patches content
|
||
|
patch: function (content, delta) {
|
||
|
var output = [],
|
||
|
chunk, i, ln;
|
||
|
|
||
|
if (delta.length === 0) {
|
||
|
return content;
|
||
|
}
|
||
|
|
||
|
for (i = 0,ln = delta.length; i < ln; i++) {
|
||
|
chunk = delta[i];
|
||
|
|
||
|
if (typeof chunk === 'number') {
|
||
|
output.push(content.substring(chunk, chunk + delta[++i]));
|
||
|
}
|
||
|
else {
|
||
|
output.push(chunk);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return output.join('');
|
||
|
},
|
||
|
|
||
|
checkAllUpdates: function() {
|
||
|
//<debug>
|
||
|
_debug("Checking for All Updates");
|
||
|
//</debug>
|
||
|
if(window['removeEventListener']) {
|
||
|
window.removeEventListener('online', Microloader.checkAllUpdates, false);
|
||
|
}
|
||
|
|
||
|
if(_cache) {
|
||
|
Microloader.checkForAppCacheUpdate();
|
||
|
}
|
||
|
|
||
|
// Manifest came from a cached instance, check for updates
|
||
|
if (Microloader.manifest.fromCache) {
|
||
|
Microloader.checkForUpdates();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
checkForAppCacheUpdate: function() {
|
||
|
//<debug>
|
||
|
_debug("Checking App Cache status");
|
||
|
//</debug>
|
||
|
if (_cache.status === _cache.UPDATEREADY || _cache.status === _cache.OBSOLETE) {
|
||
|
//<debug>
|
||
|
_debug("App Cache is already in an updated");
|
||
|
//</debug>
|
||
|
Microloader.appCacheState = 'updated';
|
||
|
} else if (_cache.status !== _cache.IDLE && _cache.status !== _cache.UNCACHED) {
|
||
|
//<debug>
|
||
|
_debug("App Cache is checking or downloading updates, adding listeners");
|
||
|
//</debug>
|
||
|
Microloader.appCacheState = 'checking';
|
||
|
_cache.addEventListener('error', Microloader.onAppCacheError);
|
||
|
_cache.addEventListener('noupdate', Microloader.onAppCacheNotUpdated);
|
||
|
_cache.addEventListener('cached', Microloader.onAppCacheNotUpdated);
|
||
|
_cache.addEventListener('updateready', Microloader.onAppCacheReady);
|
||
|
_cache.addEventListener('obsolete', Microloader.onAppCacheObsolete);
|
||
|
} else {
|
||
|
//<debug>
|
||
|
_debug("App Cache is current or uncached");
|
||
|
//</debug>
|
||
|
Microloader.appCacheState = 'current';
|
||
|
}
|
||
|
},
|
||
|
|
||
|
checkForUpdates: function() {
|
||
|
// Fetch the Latest Manifest from the server
|
||
|
//<debug>
|
||
|
_debug("Checking for updates at: " + Microloader.manifest.url);
|
||
|
//</debug>
|
||
|
Boot.fetch(Microloader.applyCacheBuster(Microloader.manifest.url), Microloader.onUpdatedManifestLoaded);
|
||
|
},
|
||
|
|
||
|
onAppCacheError: function(e) {
|
||
|
_warn(e.message);
|
||
|
|
||
|
Microloader.appCacheState = 'error';
|
||
|
Microloader.notifyUpdateReady();
|
||
|
},
|
||
|
|
||
|
onAppCacheReady: function() {
|
||
|
_cache.swapCache();
|
||
|
Microloader.appCacheUpdated();
|
||
|
},
|
||
|
|
||
|
onAppCacheObsolete: function() {
|
||
|
Microloader.appCacheUpdated();
|
||
|
},
|
||
|
|
||
|
appCacheUpdated: function() {
|
||
|
//<debug>
|
||
|
_debug("App Cache Updated");
|
||
|
//</debug>
|
||
|
Microloader.appCacheState = 'updated';
|
||
|
Microloader.notifyUpdateReady();
|
||
|
},
|
||
|
|
||
|
onAppCacheNotUpdated: function() {
|
||
|
//<debug>
|
||
|
_debug("App Cache Not Updated Callback");
|
||
|
//</debug>
|
||
|
Microloader.appCacheState = 'current';
|
||
|
Microloader.notifyUpdateReady();
|
||
|
},
|
||
|
|
||
|
|
||
|
filterAsset: function(asset) {
|
||
|
var cfg = (asset && asset.assetConfig) || {};
|
||
|
if(cfg.platform || cfg.exclude) {
|
||
|
return Boot.filterPlatform(cfg.platform, cfg.exclude);
|
||
|
}
|
||
|
return true;
|
||
|
},
|
||
|
|
||
|
onUpdatedManifestLoaded: function (result) {
|
||
|
result = Microloader.parseResult(result);
|
||
|
|
||
|
if (!result.error) {
|
||
|
var currentAssets, newAssets, currentAsset, newAsset, prop,
|
||
|
assets, deltas, deltaPath, include,
|
||
|
updatingAssets = [],
|
||
|
manifest = new Manifest({
|
||
|
url: Microloader.manifest.url,
|
||
|
content: result.content,
|
||
|
assetCache: false
|
||
|
});
|
||
|
|
||
|
Microloader.remainingUpdatingAssets = 0;
|
||
|
Microloader.updatedAssets = [];
|
||
|
Microloader.removedAssets = [];
|
||
|
Microloader.updatedManifest = null;
|
||
|
Microloader.updatedAssetsReady = false;
|
||
|
|
||
|
// If the updated manifest has turned off caching we need to clear out all local storage
|
||
|
// and trigger a appupdate as all content is now uncached
|
||
|
if (!manifest.shouldCache()) {
|
||
|
//<debug>
|
||
|
_debug("New Manifest has caching disabled, clearing out any private storage");
|
||
|
//</debug>
|
||
|
|
||
|
Microloader.updatedManifest = manifest;
|
||
|
LocalStorage.clearAllPrivate(manifest);
|
||
|
Microloader.onAllUpdatedAssetsReady();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Manifest itself has changed
|
||
|
if (!Microloader.manifest.is(manifest)) {
|
||
|
Microloader.updatedManifest = manifest;
|
||
|
|
||
|
currentAssets = Microloader.manifest.getAssets();
|
||
|
newAssets = manifest.getAssets();
|
||
|
|
||
|
// Look through new assets for assets that do not exist or assets that have different versions
|
||
|
for (prop in newAssets) {
|
||
|
newAsset = newAssets[prop];
|
||
|
currentAsset = Microloader.manifest.getAsset(newAsset.assetConfig.path);
|
||
|
include = Microloader.filterAsset(newAsset);
|
||
|
|
||
|
if (include && (!currentAsset || (newAsset.shouldCache() && (!currentAsset.is(newAsset))))) {
|
||
|
//<debug>
|
||
|
_debug("New/Updated Version of Asset: " + newAsset.assetConfig.path + " was found in new manifest");
|
||
|
//</debug>
|
||
|
updatingAssets.push({_new: newAsset, _current: currentAsset});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Look through current assets for stale/old assets that have been removed
|
||
|
for (prop in currentAssets) {
|
||
|
currentAsset = currentAssets[prop];
|
||
|
newAsset = manifest.getAsset(currentAsset.assetConfig.path);
|
||
|
|
||
|
//New version of this asset has been filtered out
|
||
|
include = !Microloader.filterAsset(newAsset);
|
||
|
|
||
|
if (!include || !newAsset || (currentAsset.shouldCache() && !newAsset.shouldCache())) {
|
||
|
//<debug>
|
||
|
_debug("Asset: " + currentAsset.assetConfig.path + " was not found in new manifest, has been filtered out or has been switched to not cache. Marked for removal");
|
||
|
//</debug>
|
||
|
Microloader.removedAssets.push(currentAsset);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Loop through all assets that need updating
|
||
|
if (updatingAssets.length > 0) {
|
||
|
Microloader.remainingUpdatingAssets = updatingAssets.length;
|
||
|
while (updatingAssets.length > 0) {
|
||
|
assets = updatingAssets.pop();
|
||
|
newAsset = assets._new;
|
||
|
currentAsset = assets._current;
|
||
|
|
||
|
// Full Updates will simply download the file and replace its current content
|
||
|
if (newAsset.assetConfig.update === "full" || !currentAsset) {
|
||
|
|
||
|
//<debug>
|
||
|
if (newAsset.assetConfig.update === "delta") {
|
||
|
_debug("Delta updated asset found without current asset available: " + newAsset.assetConfig.path + " fetching full file");
|
||
|
} else {
|
||
|
_debug("Full update found for: " + newAsset.assetConfig.path + " fetching");
|
||
|
}
|
||
|
//</debug>
|
||
|
|
||
|
// Load the asset and cache its its content into Boot to be evaluated in sequence
|
||
|
Boot.fetch(newAsset.assetConfig.path, (function (asset) {
|
||
|
return function (result) {
|
||
|
Microloader.onFullAssetUpdateLoaded(asset, result)
|
||
|
};
|
||
|
}(newAsset))
|
||
|
);
|
||
|
|
||
|
// Delta updates will be given a delta patch
|
||
|
} else if (newAsset.assetConfig.update === "delta") {
|
||
|
deltas = manifest.deltas;
|
||
|
deltaPath = deltas + "/" + newAsset.assetConfig.path + "/" + currentAsset.assetConfig.hash + ".json";
|
||
|
// Fetch the Delta Patch and update the contents of the asset
|
||
|
//<debug>
|
||
|
_debug("Delta update found for: " + newAsset.assetConfig.path + " fetching");
|
||
|
//</debug>
|
||
|
Boot.fetch(deltaPath,
|
||
|
(function (asset, oldAsset) {
|
||
|
return function (result) {
|
||
|
Microloader.onDeltaAssetUpdateLoaded(asset, oldAsset, result)
|
||
|
};
|
||
|
}(newAsset, currentAsset))
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
//<debug>
|
||
|
_debug("No Assets needed updating");
|
||
|
//</debug>
|
||
|
Microloader.onAllUpdatedAssetsReady();
|
||
|
}
|
||
|
} else {
|
||
|
//<debug>
|
||
|
_debug("Manifest files have matching hash's");
|
||
|
//</debug>
|
||
|
Microloader.onAllUpdatedAssetsReady();
|
||
|
}
|
||
|
} else {
|
||
|
_warn("Error loading manifest file to check for updates");
|
||
|
Microloader.onAllUpdatedAssetsReady();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
onFullAssetUpdateLoaded: function(asset, result) {
|
||
|
var checksum;
|
||
|
result = Microloader.parseResult(result);
|
||
|
Microloader.remainingUpdatingAssets--;
|
||
|
|
||
|
if (!result.error) {
|
||
|
checksum = Microloader.checksum(result.content, asset.assetConfig.hash);
|
||
|
//<debug>
|
||
|
_debug("Checksum for Full asset: " + asset.assetConfig.path + " is " + checksum);
|
||
|
//</debug>
|
||
|
if (!checksum) {
|
||
|
//<debug>
|
||
|
_debug("Full Update Asset: " + asset.assetConfig.path + " has failed checksum. This asset will be uncached for future loading");
|
||
|
//</debug>
|
||
|
|
||
|
// uncache this asset as there is a new version somewhere that has not been loaded.
|
||
|
asset.uncache();
|
||
|
} else {
|
||
|
asset.updateContent(result.content);
|
||
|
Microloader.updatedAssets.push(asset);
|
||
|
}
|
||
|
} else {
|
||
|
//<debug>
|
||
|
_debug("Error loading file at" + asset.assetConfig.path + ". This asset will be uncached for future loading");
|
||
|
//</debug>
|
||
|
|
||
|
// uncache this asset as there is a new version somewhere that has not been loaded.
|
||
|
asset.uncache();
|
||
|
}
|
||
|
|
||
|
if (Microloader.remainingUpdatingAssets === 0) {
|
||
|
Microloader.onAllUpdatedAssetsReady();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
onDeltaAssetUpdateLoaded: function(asset, oldAsset, result) {
|
||
|
var json, checksum, content;
|
||
|
result = Microloader.parseResult(result);
|
||
|
Microloader.remainingUpdatingAssets--;
|
||
|
|
||
|
if (!result.error) {
|
||
|
//<debug>
|
||
|
_debug("Delta patch loaded successfully, patching content");
|
||
|
//</debug>
|
||
|
try {
|
||
|
json = JSON.parse(result.content);
|
||
|
content = Microloader.patch(oldAsset.content, json);
|
||
|
checksum = Microloader.checksum(content, asset.assetConfig.hash);
|
||
|
//<debug>
|
||
|
_debug("Checksum for Delta Patched asset: " + asset.assetConfig.path + " is " + checksum);
|
||
|
//</debug>
|
||
|
if (!checksum) {
|
||
|
//<debug>
|
||
|
_debug("Delta Update Asset: " + asset.assetConfig.path + " has failed checksum. This asset will be uncached for future loading");
|
||
|
//</debug>
|
||
|
|
||
|
// uncache this asset as there is a new version somewhere that has not been loaded.
|
||
|
asset.uncache();
|
||
|
} else {
|
||
|
asset.updateContent(content);
|
||
|
Microloader.updatedAssets.push(asset);
|
||
|
}
|
||
|
} catch (e) {
|
||
|
_warn("Error parsing delta patch for " + asset.assetConfig.path + " with hash " + oldAsset.assetConfig.hash + " . This asset will be uncached for future loading");
|
||
|
// uncache this asset as there is a new version somewhere that has not been loaded.
|
||
|
asset.uncache();
|
||
|
}
|
||
|
} else {
|
||
|
_warn("Error loading delta patch for " + asset.assetConfig.path + " with hash " + oldAsset.assetConfig.hash + " . This asset will be uncached for future loading");
|
||
|
|
||
|
// uncache this asset as there is a new version somewhere that has not been loaded.
|
||
|
asset.uncache();
|
||
|
}
|
||
|
if (Microloader.remainingUpdatingAssets === 0) {
|
||
|
Microloader.onAllUpdatedAssetsReady();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
//TODO: Make this all transaction based to allow for reverting if quota is exceeded
|
||
|
onAllUpdatedAssetsReady: function() {
|
||
|
var asset;
|
||
|
Microloader.updatedAssetsReady = true;
|
||
|
|
||
|
if (Microloader.updatedManifest) {
|
||
|
while (Microloader.removedAssets.length > 0) {
|
||
|
asset = Microloader.removedAssets.pop();
|
||
|
//<debug>
|
||
|
_debug("Asset: " + asset.assetConfig.path + " was removed, un-caching");
|
||
|
//</debug>
|
||
|
asset.uncache();
|
||
|
}
|
||
|
|
||
|
if (Microloader.updatedManifest) {
|
||
|
//<debug>
|
||
|
_debug("Manifest was updated, re-caching");
|
||
|
//</debug>
|
||
|
Microloader.updatedManifest.cache();
|
||
|
}
|
||
|
|
||
|
while (Microloader.updatedAssets.length > 0) {
|
||
|
asset = Microloader.updatedAssets.pop();
|
||
|
//<debug>
|
||
|
_debug("Asset: " + asset.assetConfig.path + " was updated, re-caching");
|
||
|
//</debug>
|
||
|
asset.cache();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
Microloader.notifyUpdateReady();
|
||
|
},
|
||
|
|
||
|
notifyUpdateReady: function () {
|
||
|
if (Microloader.appCacheState !== 'checking' && Microloader.updatedAssetsReady) {
|
||
|
if (Microloader.appCacheState === 'updated' || Microloader.updatedManifest) {
|
||
|
//<debug>
|
||
|
_debug("There was an update here you will want to reload the app, trigger an event");
|
||
|
//</debug>
|
||
|
Microloader.appUpdate = {
|
||
|
updated: true,
|
||
|
app: Microloader.appCacheState === 'updated',
|
||
|
manifest: Microloader.updatedManifest && Microloader.updatedManifest.exportContent()
|
||
|
};
|
||
|
|
||
|
Microloader.fireAppUpdate();
|
||
|
}
|
||
|
//<debug>
|
||
|
else {
|
||
|
_debug("AppCache and LocalStorage Cache are current, no updating needed");
|
||
|
Microloader.appUpdate = {};
|
||
|
}
|
||
|
//</debug>
|
||
|
}
|
||
|
},
|
||
|
|
||
|
fireAppUpdate: function() {
|
||
|
if (Ext.GlobalEvents) {
|
||
|
// We defer dispatching this event slightly in order to let the application finish loading
|
||
|
// as we are still very early in the lifecycle
|
||
|
Ext.defer(function() {
|
||
|
Ext.GlobalEvents.fireEvent('appupdate', Microloader.appUpdate);
|
||
|
}, 100);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
checksum: function(content, hash) {
|
||
|
if(!content || !hash) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
var passed = true,
|
||
|
hashLn = hash.length,
|
||
|
checksumType = content.substring(0, 1);
|
||
|
|
||
|
if (checksumType == '/') {
|
||
|
if (content.substring(2, hashLn + 2) !== hash) {
|
||
|
passed = false;
|
||
|
}
|
||
|
} else if (checksumType == 'f') {
|
||
|
if (content.substring(10, hashLn + 10) !== hash) {
|
||
|
passed = false;
|
||
|
}
|
||
|
} else if (checksumType == '.') {
|
||
|
if (content.substring(1, hashLn + 1) !== hash) {
|
||
|
passed = false;
|
||
|
}
|
||
|
}
|
||
|
return passed;
|
||
|
},
|
||
|
parseResult: function(result) {
|
||
|
var rst = {};
|
||
|
if ((result.exception || result.status === 0) && !Boot.env.phantom) {
|
||
|
rst.error = true;
|
||
|
} else if ((result.status >= 200 && result.status < 300) || result.status === 304
|
||
|
|| Boot.env.phantom
|
||
|
|| (result.status === 0 && result.content.length > 0)
|
||
|
) {
|
||
|
rst.content = result.content;
|
||
|
} else {
|
||
|
rst.error = true;
|
||
|
}
|
||
|
return rst;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
return Microloader;
|
||
|
}());
|
||
|
|
||
|
/**
|
||
|
* @type {String/Object}
|
||
|
*/
|
||
|
Ext.manifest = Ext.manifest || "bootstrap";
|
||
|
|
||
|
Ext.Microloader.run();
|