2015-12-23 00:16:54 +00:00
|
|
|
'use strict';
|
|
|
|
/*
|
|
|
|
* Copyright (c) 2015-2016, Willem Cazander
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
|
|
|
|
* that the following conditions are met:
|
|
|
|
*
|
|
|
|
* * Redistributions of source code must retain the above copyright notice, this list of conditions and the
|
|
|
|
* following disclaimer.
|
|
|
|
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
|
|
|
|
* the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
|
|
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
|
|
|
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
|
|
|
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
// TODO:
|
|
|
|
// set tag.media = 'only you';
|
|
|
|
// add media in resouces
|
2016-01-14 21:42:13 +00:00
|
|
|
// check supported browsers
|
2015-12-23 00:16:54 +00:00
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
/**
|
|
|
|
* FFSpaLoader is an assets loader for single page applications.
|
|
|
|
* Its build around the concept the there is only a single static index.html which
|
|
|
|
* synces all its assets to local cache for offline and or fast page loads.
|
|
|
|
*
|
|
|
|
* Features:
|
|
|
|
* - Assets caching for offline use.
|
|
|
|
* - Assets hashing for fast syncing.
|
|
|
|
* - Assets types: js,css,cssData
|
|
|
|
* - Caching backends: null,localStorage,webSqlDB,sqllite
|
|
|
|
* - Optional ask and cache server assets url.
|
|
|
|
* - Cordova deviceready event booting.
|
|
|
|
*
|
|
|
|
* @module FFSpaLoader
|
|
|
|
*/
|
|
|
|
var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
|
2015-12-23 00:16:54 +00:00
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
var options = {
|
|
|
|
debug: false,
|
|
|
|
errorHandler: null,
|
|
|
|
bootDevice: {
|
|
|
|
timeout: 4096,
|
|
|
|
windowFlag: 'FFCordovaDevice'
|
|
|
|
},
|
|
|
|
askUrl: {
|
|
|
|
urlTransport: 'http://',
|
|
|
|
questionText: 'Please provide the server name;',
|
|
|
|
cssText: '.ffAskUrl { font-size: 1em;margin: auto;width: 50%;border: 3px solid #73AD21;padding: 1em;} .ffAskUrl > div {font-size: 0.8em;color: #ccc;} .ffAskUrl > div > * {} .ffAskUrl > div > input {} .ffAskUrlError{ color: red}',
|
|
|
|
},
|
|
|
|
server: {
|
|
|
|
url: null,
|
|
|
|
urlPath: null,
|
|
|
|
timeout: 4096,
|
|
|
|
windowFlag: 'FFServerUrl',
|
|
|
|
header: {
|
|
|
|
request: {
|
|
|
|
'X-FFSpaLoader': '42'
|
|
|
|
},
|
|
|
|
response: {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
cache: {
|
|
|
|
factory: null,
|
|
|
|
meta: null,
|
|
|
|
js: null,
|
|
|
|
css: null,
|
|
|
|
cssData: null
|
|
|
|
}
|
|
|
|
};
|
2015-12-23 00:16:54 +00:00
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
/**
|
|
|
|
* Prints debug message to the console if options.debug is true.
|
|
|
|
* @param {String} message The message to log.
|
|
|
|
* @private
|
|
|
|
*/
|
2015-12-23 00:16:54 +00:00
|
|
|
var utilDebug = function (message) {
|
|
|
|
if (options.debug !== true) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
console.log('FFSpaLoader.'+message);
|
|
|
|
};
|
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
var factory = {
|
|
|
|
detect: {
|
|
|
|
localStorage: function() {
|
|
|
|
try {
|
|
|
|
var testData = 'localStorageDetect';
|
|
|
|
localStorage.setItem(testData, testData);
|
|
|
|
localStorage.removeItem(testData);
|
|
|
|
return true;
|
|
|
|
} catch(e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
openDatabase: function() {
|
|
|
|
return 'openDatabase' in window;
|
|
|
|
},
|
|
|
|
sqlitePlugin: function() {
|
|
|
|
return 'sqlitePlugin' in window;
|
|
|
|
},
|
|
|
|
cordova: function() {
|
|
|
|
return 'cordova' in window;
|
|
|
|
},
|
|
|
|
cordovaDevice: function() {
|
|
|
|
return options.bootDevice.windowFlag in window;
|
|
|
|
},
|
|
|
|
mobileAgent: function() {
|
|
|
|
return navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|IEMobile)/);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
cache: {
|
|
|
|
localStorage: function() {
|
|
|
|
return {
|
|
|
|
cacheGetValue: function(key, cb) {
|
|
|
|
try {
|
|
|
|
var dataRaw = localStorage.getItem(key);
|
|
|
|
var data = JSON.parse(dataRaw);
|
|
|
|
cb(null, data);
|
|
|
|
} catch(e) {
|
|
|
|
cb(e);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
cacheSetValue: function(key, value, cb) {
|
|
|
|
try {
|
|
|
|
localStorage.setItem(key,JSON.stringify(value));
|
|
|
|
cb(null);
|
|
|
|
} catch(e) {
|
|
|
|
cb(e);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
cacheDeleteValue: function(key, cb) {
|
|
|
|
try {
|
|
|
|
localStorage.removeItem(key);
|
|
|
|
cb(null);
|
|
|
|
} catch(e) {
|
|
|
|
cb(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
websql: function(opt) {
|
|
|
|
if (opt === undefined) { opt = {}; }
|
|
|
|
if (opt.name === undefined) { opt.name = 'ffSpaLoader.db'; }
|
|
|
|
if (opt.size === undefined) { opt.size = -1; }
|
|
|
|
if (opt.version === undefined) { opt.version = '1.0'; }
|
|
|
|
if (opt.openDatabase === undefined) { opt.openDatabase = window.openDatabase; }
|
|
|
|
var nullDataHandler = function(cb) {
|
|
|
|
return function (tx, results) {
|
|
|
|
cb(null);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
var sqlErrorHandler = function(cb) {
|
|
|
|
return function (tx, err) {
|
|
|
|
cb(err.message+' code: '+err.code);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
var cacheDB = window.openDatabase(opt.name, opt.version, opt.name, opt.size);
|
|
|
|
cacheDB.transaction(function(tx) {
|
|
|
|
var query = 'SELECT value FROM cache_store WHERE key = \"test-for-table\"';
|
|
|
|
utilDebug('websql.init query: '+query);
|
|
|
|
tx.executeSql(query, [], function(tx,res) {}, function(tx,errorNoTable) {
|
|
|
|
var query = 'CREATE TABLE cache_store(id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT NOT NULL, value TEXT NOT NULL)';
|
|
|
|
utilDebug('websql.init query: '+query);
|
|
|
|
tx.executeSql(query, [], function(tx,res) {
|
2016-01-14 23:36:10 +00:00
|
|
|
var query = 'CREATE UNIQUE INDEX cache_store__key__udx ON cache_store (key)';
|
2016-01-14 21:42:13 +00:00
|
|
|
utilDebug('websql.init query: '+query);
|
|
|
|
tx.executeSql(query, [], function(tx,res) {}, sqlErrorHandler(options.errorHandler)); // FIX<E: asyns error ?
|
|
|
|
}, sqlErrorHandler(options.errorHandler));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return {
|
|
|
|
cacheGetValue: function(key, cb) {
|
|
|
|
cacheDB.transaction(function(tx) {
|
|
|
|
var query = 'SELECT value FROM cache_store WHERE key = ?';
|
|
|
|
utilDebug('websql.cacheGetValue query: '+query);
|
|
|
|
tx.executeSql(query,[key], function(tx, res) {
|
|
|
|
if (res.rows.length === 0) {
|
|
|
|
cb(null, null);
|
|
|
|
} else {
|
|
|
|
var value = res.rows.item(0).value;
|
|
|
|
cb(null, JSON.parse(value));
|
|
|
|
}
|
|
|
|
}, sqlErrorHandler(cb));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
cacheSetValue: function(key, value, cb) {
|
|
|
|
cacheDB.transaction(function(tx) {
|
|
|
|
var query = 'SELECT value FROM cache_store WHERE key = ?';
|
|
|
|
utilDebug('websql.cacheSetValue query: '+query);
|
|
|
|
tx.executeSql(query,[key], function(tx, res) {
|
|
|
|
if (res.rows.length === 0) {
|
|
|
|
var query = 'INSERT INTO cache_store (key,value) VALUES (?,?)';
|
|
|
|
utilDebug('websql.cacheSetValue query: '+query);
|
|
|
|
tx.executeSql(query, [key,JSON.stringify(value)], nullDataHandler(cb), sqlErrorHandler(cb));
|
|
|
|
} else {
|
|
|
|
var query = 'UPDATE cache_store SET value = ? WHERE key = ?';
|
|
|
|
utilDebug('websql.cacheSetValue query: '+query);
|
|
|
|
tx.executeSql(query, [JSON.stringify(value),key], nullDataHandler(cb), sqlErrorHandler(cb));
|
|
|
|
}
|
|
|
|
}, sqlErrorHandler(cb));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
cacheDeleteValue: function(key, cb) {
|
|
|
|
cacheDB.transaction(function(tx) {
|
|
|
|
var query = 'DELETE FROM cache_store WHERE key = ?';
|
|
|
|
utilDebug('websql.cacheDeleteValue query: '+query);
|
|
|
|
tx.executeSql(query, [key], nullDataHandler(cb), sqlErrorHandler(cb));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The default error handler which renders the error in the browser.
|
|
|
|
* @param {String|Error} err The error message.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
var utilErrorHandler = function(err) {
|
|
|
|
utilDebug('utilErrorHandler error: '+err);
|
|
|
|
|
|
|
|
var rootTag = document.createElement('div');
|
|
|
|
rootTag.setAttribute('class','ffError');
|
|
|
|
|
|
|
|
var cssTag = document.createElement("style");
|
|
|
|
cssTag.type = "text/css";
|
|
|
|
cssTag.innerHTML = '.ffError { margin: auto;width: 50%;border: 3px solid red;padding: 1em;} .ffAskUrl > div {font-size: 0.8em;color: red;}';
|
|
|
|
rootTag.appendChild(cssTag);
|
|
|
|
|
|
|
|
var titleTag = document.createElement('h1');
|
|
|
|
titleTag.appendChild(document.createTextNode('Error'));
|
|
|
|
rootTag.appendChild(titleTag);
|
|
|
|
|
|
|
|
var questionTag = document.createElement('p');
|
|
|
|
questionTag.appendChild(document.createTextNode(err));
|
|
|
|
rootTag.appendChild(questionTag);
|
|
|
|
|
|
|
|
document.getElementsByTagName('body')[0].appendChild(rootTag);
|
|
|
|
};
|
|
|
|
options.errorHandler = utilErrorHandler;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fetches an url resource with a XMLHttpRequest.
|
|
|
|
* @param {String} url The url to fetch.
|
|
|
|
* @param {function} cb The callback for error and request if ready.
|
|
|
|
* @private
|
|
|
|
*/
|
2015-12-23 00:16:54 +00:00
|
|
|
var utilHttpFetch = function (url, cb) {
|
|
|
|
var startTime = new Date().getTime();
|
|
|
|
var httpRequest = new XMLHttpRequest();
|
|
|
|
httpRequest.onreadystatechange = function() {
|
2016-01-14 21:42:13 +00:00
|
|
|
if (httpRequest.readyState == 4 && httpRequest.status === 200) {
|
2015-12-23 00:16:54 +00:00
|
|
|
utilDebug('utilHttpFetch url: '+url+' done in '+(new Date().getTime()-startTime)+' ms.');
|
2016-01-14 21:42:13 +00:00
|
|
|
cb(null, httpRequest);
|
|
|
|
} else if (httpRequest.readyState == 4) {
|
|
|
|
cb('Wrong status: '+httpRequest.status);
|
2015-12-23 00:16:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
httpRequest.open('GET', url, true);
|
2016-01-14 21:42:13 +00:00
|
|
|
httpRequest.timeout = options.server.timeout; // ieX: after open()
|
|
|
|
httpRequest.ontimeout = function() {
|
|
|
|
cb('timeout after '+options.server.timeout+' of url: '+url);
|
|
|
|
};
|
2015-12-23 00:16:54 +00:00
|
|
|
httpRequest.send();
|
|
|
|
};
|
2016-01-14 21:42:13 +00:00
|
|
|
|
|
|
|
var cacheGetService = function (type) {
|
|
|
|
if (options.cache[type]) {
|
|
|
|
return options.cache[type];
|
2015-12-23 00:16:54 +00:00
|
|
|
}
|
2016-01-14 21:42:13 +00:00
|
|
|
if (options.cache['factory']) {
|
|
|
|
return options.cache['factory'];
|
2015-12-23 00:16:54 +00:00
|
|
|
}
|
2016-01-14 21:42:13 +00:00
|
|
|
return null;
|
|
|
|
};
|
2015-12-23 00:16:54 +00:00
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
var cacheHasService = function (type) {
|
|
|
|
return cacheGetService(type) !== null;
|
|
|
|
};
|
|
|
|
|
|
|
|
var cacheCheckType = function (type, cb, action) {
|
|
|
|
cacheHasService(type)?action():cb('No caching for: '+type);
|
|
|
|
};
|
|
|
|
|
|
|
|
var cacheGetValue = function(type, key , cb) {
|
|
|
|
cacheCheckType(type, cb, function() {
|
|
|
|
var cacheKey = type+'_'+key;
|
|
|
|
utilDebug('cacheGetValue key: '+cacheKey);
|
|
|
|
cacheGetService(type).cacheGetValue(cacheKey,cb);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
var cacheSetValue = function(type, key, value, cb) {
|
|
|
|
cacheCheckType(type, cb, function() {
|
|
|
|
var cacheKey = type+'_'+key;
|
|
|
|
utilDebug('cacheSetValue key: '+cacheKey);
|
|
|
|
cacheGetService(type).cacheSetValue(cacheKey,value,cb);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
var cacheDeleteValue = function(type, key, cb) {
|
|
|
|
cacheCheckType(type, cb, function() {
|
|
|
|
var cacheKey = type+'_'+key;
|
|
|
|
utilDebug('cacheDeleteValue key: '+cacheKey);
|
|
|
|
cacheGetService(type).cacheDeleteValue(cacheKey,cb);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
var cleanupCache = function (resources, cb) {
|
|
|
|
utilDebug('cleanupCache TODO cacheList: '+0+' resources: '+resources.length);
|
2015-12-23 00:16:54 +00:00
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
// TODO: impl for removes in resource lists
|
|
|
|
cb(null);
|
|
|
|
};
|
2015-12-23 00:16:54 +00:00
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
var injectResourceData = function(resource, data, cb) {
|
|
|
|
utilDebug('injectResourceData resource: '+JSON.stringify(resource)+' data: '+data.length);
|
|
|
|
var tag = null;
|
|
|
|
if (resource.type === 'css' || resource.type === 'cssData') {
|
|
|
|
tag = document.createElement('style');
|
|
|
|
tag.type = 'text/css';
|
2015-12-23 00:16:54 +00:00
|
|
|
}
|
2016-01-14 21:42:13 +00:00
|
|
|
if (resource.type === 'js') {
|
|
|
|
tag = document.createElement('script');
|
|
|
|
tag.type = 'text/javascript';
|
2015-12-23 00:16:54 +00:00
|
|
|
}
|
2016-01-14 21:42:13 +00:00
|
|
|
tag.appendChild(document.createTextNode(data));
|
2015-12-23 00:16:54 +00:00
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
document.getElementsByTagName('head')[0].appendChild(tag);
|
|
|
|
//var ref = document.getElementsByTagName('script')[0];
|
|
|
|
//ref.parentNode.insertBefore(tag, ref); // note in reverse order
|
|
|
|
cb(null);
|
|
|
|
};
|
2015-12-23 00:16:54 +00:00
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
var storeResourceKey = function (resource, cb) {
|
|
|
|
// Add all cache keys in central list so we can clear the cache item if resources are removed.
|
|
|
|
var cacheKey = resource.type+'_keys';
|
|
|
|
cacheGetValue('meta',cacheKey,function (err,value) {
|
|
|
|
if (err !== null) {
|
|
|
|
cb(err);
|
2015-12-23 00:16:54 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-01-14 21:42:13 +00:00
|
|
|
if (value === null) {
|
|
|
|
value = [];
|
|
|
|
}
|
|
|
|
value.push(resource.hash);
|
|
|
|
cacheSetValue('meta',cacheKey,value, cb);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
var storeResource = function (resource, httpRequest, cb) {
|
|
|
|
utilDebug('storeResource url: '+resource.url+' hash: '+resource.hash);
|
|
|
|
var item = {
|
|
|
|
resource: resource,
|
|
|
|
data: httpRequest.responseText
|
2015-12-23 00:16:54 +00:00
|
|
|
}
|
2016-01-14 21:42:13 +00:00
|
|
|
cacheSetValue(resource.type, resource.hash, item , function(err) {
|
|
|
|
if (err !== null) {
|
|
|
|
cb(err);
|
2015-12-23 00:16:54 +00:00
|
|
|
} else {
|
2016-01-14 21:42:13 +00:00
|
|
|
storeResourceKey(resource,cb);
|
2015-12-23 00:16:54 +00:00
|
|
|
}
|
|
|
|
});
|
2016-01-14 21:42:13 +00:00
|
|
|
};
|
2015-12-23 00:16:54 +00:00
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
var loadResource = function (resource, cb) {
|
|
|
|
var resourceUrl = resource.url;
|
|
|
|
if (resourceUrl.indexOf('http') === -1) {
|
|
|
|
resourceUrl = options.server.url + resourceUrl;
|
|
|
|
}
|
|
|
|
utilDebug('loadResource '+JSON.stringify(resource));
|
|
|
|
if (cacheHasService(resource.type)) {
|
|
|
|
cacheGetValue(resource.type,resource.hash,function(err, value) {
|
|
|
|
if (err !== null) {
|
|
|
|
cb(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (value === null) {
|
|
|
|
utilDebug('loadResource cache miss');
|
|
|
|
} else if (value.resource === undefined) {
|
|
|
|
utilDebug('loadResource cache wrong obj');
|
|
|
|
} else if (value.resource.hash !== resource.hash) {
|
|
|
|
utilDebug('loadResource cache hash mismatch');
|
|
|
|
} else {
|
|
|
|
utilDebug('loadResource cache hit');
|
|
|
|
injectResourceData(value.resource,value.data,cb);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
utilHttpFetch(resourceUrl, function(err, httpRequest) {
|
|
|
|
if (err !== null) {
|
|
|
|
cb(err);
|
|
|
|
} else {
|
|
|
|
storeResource(resource, httpRequest, function (err) {
|
|
|
|
injectResourceData(resource,httpRequest.responseText,cb);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// note: was links but now download + inject so order stays sequenced
|
|
|
|
utilHttpFetch(resourceUrl, function(err, httpRequest) {
|
|
|
|
if (err !== null) {
|
|
|
|
cb(err);
|
|
|
|
} else {
|
|
|
|
injectResourceData(resource,httpRequest.responseText,cb);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var loadResources = function(resources, cb) {
|
2015-12-23 00:16:54 +00:00
|
|
|
var startTime = new Date().getTime();
|
2016-01-14 21:42:13 +00:00
|
|
|
utilDebug('loadResources');
|
2015-12-23 00:16:54 +00:00
|
|
|
var resourceStack = resources;
|
2016-01-14 21:42:13 +00:00
|
|
|
var resourceLoader = function(err) {
|
|
|
|
if (err !== null) {
|
|
|
|
cb(err);
|
|
|
|
return;
|
|
|
|
}
|
2015-12-23 00:16:54 +00:00
|
|
|
resourceStack = resourceStack.slice(1);
|
|
|
|
if (resourceStack.length === 0) {
|
2016-01-14 21:42:13 +00:00
|
|
|
utilDebug('loadResources done in '+(new Date().getTime()-startTime)+' ms.');
|
|
|
|
cb(null);
|
2015-12-23 00:16:54 +00:00
|
|
|
} else {
|
2016-01-14 21:42:13 +00:00
|
|
|
loadResource(resourceStack[0],resourceLoader);
|
2015-12-23 00:16:54 +00:00
|
|
|
}
|
|
|
|
};
|
2016-01-14 21:42:13 +00:00
|
|
|
loadResource(resourceStack[0],resourceLoader);
|
|
|
|
};
|
2015-12-23 00:16:54 +00:00
|
|
|
|
|
|
|
var injectResources = function(resources, cb) {
|
|
|
|
var startTime = new Date().getTime();
|
|
|
|
utilDebug('injectResources');
|
|
|
|
resources.forEach(function (resource) {
|
2016-01-14 21:42:13 +00:00
|
|
|
cacheGetValue(resource.type,resource.hash,function(err,item) {
|
|
|
|
// TODO injectResourceData
|
|
|
|
|
|
|
|
var tag = null;
|
|
|
|
if (resource.type === 'css' || resource.type === 'cssData') {
|
|
|
|
tag = document.createElement('style');
|
|
|
|
tag.type = 'text/css';
|
|
|
|
}
|
|
|
|
if (resource.type === 'js') {
|
|
|
|
tag = document.createElement('script');
|
|
|
|
tag.type = 'text/javascript';
|
|
|
|
}
|
|
|
|
utilDebug('injectResources resource: '+JSON.stringify(resource));
|
|
|
|
|
|
|
|
tag.appendChild(document.createTextNode(item.data));
|
|
|
|
|
|
|
|
//document.getElementsByTagName('head')[0].appendChild(tag);
|
|
|
|
var ref = document.getElementsByTagName('script')[0];
|
|
|
|
ref.parentNode.insertBefore(tag, ref); // note in reverse order
|
|
|
|
});
|
2015-12-23 00:16:54 +00:00
|
|
|
});
|
|
|
|
utilDebug('injectResources done in '+(new Date().getTime()-startTime)+' ms.');
|
2016-01-14 21:42:13 +00:00
|
|
|
cb(null); // FIXME async
|
|
|
|
};
|
2015-12-23 00:16:54 +00:00
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
/**
|
|
|
|
* Starts loader by downloading resource list or using cache version to
|
|
|
|
* load/refresh/inject the resources.
|
|
|
|
*
|
|
|
|
* @param {function} cb Callback gets called when loader is done.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
var startLoader = function (cb) {
|
|
|
|
if (options.server.url === null) {
|
|
|
|
cb('Missing options.server.url');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (options.server.urlPath === null) {
|
|
|
|
cb('Missing options.server.urlPath');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
window[options.server.windowFlag] = options.server.url;
|
|
|
|
|
|
|
|
var resourcesUrl = options.server.url + options.server.urlPath;
|
|
|
|
utilDebug('startLoader serverUrl: '+resourcesUrl);
|
|
|
|
utilHttpFetch(resourcesUrl, function(err, httpRequest) {
|
|
|
|
if (err !== null) {
|
|
|
|
utilDebug('startLoader fetch error: '+err);
|
|
|
|
if (cacheHasService('meta')) {
|
|
|
|
cacheGetValue('meta','server_resources',function(err, value) {
|
|
|
|
if (err !== null) {
|
|
|
|
cb(err);
|
|
|
|
} else if (value === null) {
|
|
|
|
cb('Have no cache of server resouces from: '+options.server.url);
|
|
|
|
} else {
|
|
|
|
injectResources(value, cb);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
cb('Could not fetch server resouces from: '+options.server.url);
|
|
|
|
}
|
|
|
|
return;
|
2015-12-23 00:16:54 +00:00
|
|
|
}
|
2016-01-14 21:42:13 +00:00
|
|
|
var resources = [];
|
|
|
|
JSON.parse(httpRequest.responseText).data.resources.forEach(function (r) {
|
|
|
|
resources.push(r);
|
|
|
|
});
|
|
|
|
utilDebug('startLoader resources: '+resources.length);
|
|
|
|
if (cacheHasService('meta')) {
|
|
|
|
cacheSetValue('meta','server_resources',resources, function (err) {
|
|
|
|
if (err !== null) {
|
|
|
|
cb(err);
|
|
|
|
} else {
|
|
|
|
loadResources(resources, cb);
|
|
|
|
}
|
|
|
|
});
|
2015-12-23 00:16:54 +00:00
|
|
|
} else {
|
2016-01-14 21:42:13 +00:00
|
|
|
loadResources(resources, cb);
|
2015-12-23 00:16:54 +00:00
|
|
|
}
|
|
|
|
});
|
2016-01-14 21:42:13 +00:00
|
|
|
};
|
2015-12-23 00:16:54 +00:00
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
/**
|
|
|
|
* Validates the user input url by downloading it.
|
|
|
|
*
|
|
|
|
* @param {function} cb Callback gets called when loader is done.
|
|
|
|
* @param {Element} deleteTag The dom element to remove from the body tag after success.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
var askUrlValidate = function (cb,deleteTag) {
|
|
|
|
var inputTag = document.getElementById('serverInput');
|
|
|
|
var inputErrorTag = document.getElementById('serverInputError');
|
|
|
|
|
|
|
|
while (inputErrorTag.firstChild) {
|
|
|
|
inputErrorTag.removeChild(inputErrorTag.firstChild); // clear error
|
2015-12-23 00:16:54 +00:00
|
|
|
}
|
2016-01-14 21:42:13 +00:00
|
|
|
|
|
|
|
options.server.url = '';
|
|
|
|
if (options.askUrl.urlTransport !== undefined) {
|
|
|
|
options.server.url += options.askUrl.urlTransport;
|
2015-12-23 00:16:54 +00:00
|
|
|
}
|
2016-01-14 21:42:13 +00:00
|
|
|
options.server.url += inputTag.value;
|
|
|
|
|
|
|
|
var resourcesUrl = options.server.url + options.server.urlPath;
|
|
|
|
utilDebug('askUrlStart check url: '+resourcesUrl);
|
|
|
|
|
|
|
|
// TODO: add+check headers
|
|
|
|
utilHttpFetch(resourcesUrl,function(err, req) {
|
|
|
|
if (err !== null) {
|
|
|
|
inputErrorTag.appendChild(document.createTextNode('Error could not get data.'));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
document.getElementsByTagName('body')[0].removeChild(deleteTag);
|
|
|
|
if (cacheHasService('meta')) {
|
|
|
|
cacheSetValue('meta','server_url',options.server.url, function(err) {
|
|
|
|
if (err !== null) {
|
|
|
|
cb(err);
|
|
|
|
} else {
|
|
|
|
startLoader(cb);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
startLoader(cb);
|
|
|
|
}
|
|
|
|
})
|
2015-12-23 00:16:54 +00:00
|
|
|
}
|
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
/**
|
|
|
|
* Creates the ask url ui.
|
|
|
|
*
|
|
|
|
* @param {function} cb Callback gets called when loader is done.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
var askUrl = function (cb) {
|
|
|
|
utilDebug('askUrl create ui');
|
|
|
|
|
|
|
|
var rootTag = document.createElement('div');
|
|
|
|
rootTag.setAttribute('class','ffAskUrl');
|
|
|
|
|
|
|
|
var cssTag = document.createElement("style");
|
|
|
|
cssTag.type = "text/css";
|
|
|
|
cssTag.innerHTML = options.askUrl.cssText;
|
|
|
|
rootTag.appendChild(cssTag);
|
|
|
|
|
|
|
|
var questionTag = document.createElement('p');
|
|
|
|
questionTag.appendChild(document.createTextNode(options.askUrl.questionText));
|
|
|
|
rootTag.appendChild(questionTag);
|
|
|
|
|
|
|
|
var formTag = document.createElement('div');
|
|
|
|
rootTag.appendChild(formTag);
|
|
|
|
|
|
|
|
if (options.urlTransport !== undefined) {
|
|
|
|
var transportTag = document.createElement('label');
|
|
|
|
rootTag.setAttribute('for','serverInput');
|
|
|
|
transportTag.appendChild(document.createTextNode(options.urlTransport));
|
|
|
|
formTag.appendChild(transportTag);
|
2015-12-23 00:16:54 +00:00
|
|
|
}
|
2016-01-14 21:42:13 +00:00
|
|
|
var inputTag = document.createElement('input');
|
|
|
|
inputTag.type = 'text';
|
|
|
|
inputTag.id = 'serverInput';
|
|
|
|
inputTag.setAttribute('autofocus','');
|
|
|
|
inputTag.setAttribute('onkeydown','if (event.keyCode == 13) {document.getElementById(\'serverSubmit\').click()}');
|
|
|
|
formTag.appendChild(inputTag);
|
2015-12-23 00:16:54 +00:00
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
var submitLineTag = document.createElement('br');
|
|
|
|
formTag.appendChild(submitLineTag);
|
2015-12-23 00:16:54 +00:00
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
var submitTag = document.createElement('input');
|
|
|
|
submitTag.id = 'serverSubmit';
|
|
|
|
submitTag.type = 'submit';
|
|
|
|
submitTag.value = 'Start';
|
|
|
|
submitTag.onclick = function() {askUrlValidate(cb,rootTag);};
|
|
|
|
formTag.appendChild(submitTag);
|
2015-12-23 00:16:54 +00:00
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
var serverErrorTag = document.createElement('div');
|
|
|
|
serverErrorTag.id = 'serverInputError';
|
|
|
|
serverErrorTag.setAttribute('class','ffAskUrlError');
|
|
|
|
formTag.appendChild(serverErrorTag);
|
|
|
|
|
|
|
|
document.getElementsByTagName('body')[0].appendChild(rootTag);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Starts the loader.
|
|
|
|
*
|
|
|
|
* @param {function} cb Callback gets called when loader is done.
|
|
|
|
*/
|
|
|
|
var start = function (cbArgu) {
|
|
|
|
var startTime = new Date().getTime();
|
|
|
|
var cb = function(err) {
|
|
|
|
if (err !== null) {
|
|
|
|
options.errorHandler(err);
|
2015-12-23 00:16:54 +00:00
|
|
|
} else {
|
2016-01-14 21:42:13 +00:00
|
|
|
utilDebug('start done in '+(new Date().getTime()-startTime)+' ms.');
|
|
|
|
if (typeof cbArgu === "function") {
|
|
|
|
cbArgu();
|
|
|
|
}
|
2015-12-23 00:16:54 +00:00
|
|
|
}
|
2016-01-14 21:42:13 +00:00
|
|
|
}
|
|
|
|
utilDebug('start serverUrl: '+options.server.url);
|
|
|
|
if (options.server.url !== null) {
|
|
|
|
startLoader(cb);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (cacheHasService('meta')) {
|
|
|
|
cacheGetValue('meta','server_url',function(err, value) {
|
|
|
|
if (err !== null) {
|
|
|
|
cb(err);
|
|
|
|
} else if (value === undefined || value === null || value === '') {
|
|
|
|
askUrl(cb);
|
|
|
|
} else {
|
|
|
|
options.server.url = value;
|
|
|
|
startLoader(cb);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
askUrl(cb);
|
|
|
|
}
|
2015-12-23 00:16:54 +00:00
|
|
|
};
|
|
|
|
|
2016-01-14 23:36:10 +00:00
|
|
|
var clearServer = function(cb) {
|
|
|
|
if (cb === undefined) {
|
|
|
|
cb = function() {};
|
|
|
|
}
|
|
|
|
if (cacheHasService('meta')) {
|
|
|
|
cacheDeleteValue('meta','server_url',function(err) {
|
|
|
|
if (err !== null) {
|
|
|
|
cb(err);
|
|
|
|
} else {
|
|
|
|
cb(null);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
cb(null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-14 21:42:13 +00:00
|
|
|
/**
|
|
|
|
* Helper for automatic selecting the best to none cache backend option.
|
|
|
|
*
|
|
|
|
* Order of detection; sqllite,openDatabase,localStorage,none.
|
|
|
|
*/
|
|
|
|
var autoSelectCache = function() {
|
|
|
|
if (factory.detect.cordovaDevice() && factory.detect.sqlitePlugin()) {
|
|
|
|
utilDebug('autoSelectCache sqlitePlugin');
|
|
|
|
return factory.cache.websql({openDatabase: window.sqlitePlugin});
|
|
|
|
}
|
|
|
|
if (factory.detect.openDatabase()) {
|
|
|
|
utilDebug('autoSelectCache openDatabase');
|
|
|
|
return factory.cache.websql();
|
|
|
|
}
|
|
|
|
if (factory.detect.localStorage()) {
|
|
|
|
utilDebug('autoSelectCache localStorage');
|
|
|
|
return factory.cache.localStorage();
|
|
|
|
}
|
|
|
|
utilDebug('autoSelectCache none');
|
|
|
|
return null; // no caching
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper for cordova applications which want to use the sqllite plugin as cache.
|
|
|
|
* Note: On none cordova page it will callback directly.
|
|
|
|
* Note: On none cordova device page is will timeout.
|
|
|
|
*
|
|
|
|
* @param {function} cb Callback gets called device is ready to use.
|
|
|
|
*/
|
|
|
|
var bootDevice = function(cb) {
|
|
|
|
var startTime = new Date().getTime();
|
|
|
|
var bootOnce = function() {
|
|
|
|
if (typeof cb === "function") {
|
|
|
|
var callback = cb;
|
|
|
|
cb = null;
|
|
|
|
utilDebug('bootDevice done in '+(new Date().getTime()-startTime)+' ms.');
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if (!factory.detect.cordova()) {
|
|
|
|
utilDebug('bootDevice webapp');
|
|
|
|
bootOnce();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
utilDebug('bootDevice timeout: '+options.bootDeviceTimeout);
|
|
|
|
setTimeout ( function () {
|
|
|
|
utilDebug('bootDevice timeout');
|
|
|
|
bootOnce();
|
|
|
|
}, options.bootDevice.timeout);
|
|
|
|
document.addEventListener("deviceready", function () {
|
|
|
|
window[options.bootDevice.windowFlag] = true;
|
|
|
|
utilDebug('bootDevice '+options.bootDevice.windowFlag);
|
|
|
|
bootOnce();
|
|
|
|
}, false);
|
2015-12-23 00:16:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
2016-01-14 21:42:13 +00:00
|
|
|
options: options,
|
|
|
|
factory: factory,
|
|
|
|
start: start,
|
|
|
|
bootDevice: bootDevice,
|
2016-01-14 23:36:10 +00:00
|
|
|
autoSelectCache: autoSelectCache,
|
|
|
|
clearServer: clearServer
|
2015-12-23 00:16:54 +00:00
|
|
|
};
|
|
|
|
})();
|